code:
<?php

/**
 * OpenID server protocol and logic.
 *
 * Overview
 *
 * An OpenID server must perform three tasks:
 *
 *  1. Examine the incoming request to determine its nature and validity.
 *  2. Make a decision about how to respond to this request.
 *  3. Format the response according to the protocol.
 *
 * The first and last of these tasks may performed by the {@link
 * Auth_OpenID_Server::decodeRequest()} and {@link
 * Auth_OpenID_Server::encodeResponse} methods.  Who gets to do the
 * intermediate task -- deciding how to respond to the request -- will
 * depend on what type of request it is.
 *
 * If it's a request to authenticate a user (a 'checkid_setup' or
 * 'checkid_immediate' request), you need to decide if you will assert
 * that this user may claim the identity in question.  Exactly how you
 * do that is a matter of application policy, but it generally
 * involves making sure the user has an account with your system and
 * is logged in, checking to see if that identity is hers to claim,
 * and verifying with the user that she does consent to releasing that
 * information to the party making the request.
 *
 * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
 * object, and if and when you've come to a decision, form a response
 * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
 *
 * Other types of requests relate to establishing associations between
 * client and server and verifing the authenticity of previous
 * communications.  {@link Auth_OpenID_Server} contains all the logic
 * and data necessary to respond to such requests; just pass it to
 * {@link Auth_OpenID_Server::handleRequest()}.
 *
 * OpenID Extensions
 *
 * Do you want to provide other information for your users in addition
 * to authentication?  Version 1.2 of the OpenID protocol allows
 * consumers to add extensions to their requests.  For example, with
 * sites using the Simple Registration
 * Extension
 * (http://openid.net/specs/openid-simple-registration-extension-1_0.html),
 * a user can agree to have their nickname and e-mail address sent to
 * a site when they sign up.
 *
 * Since extensions do not change the way OpenID authentication works,
 * code to handle extension requests may be completely separate from
 * the {@link Auth_OpenID_Request} class here.  But you'll likely want
 * data sent back by your extension to be signed.  {@link
 * Auth_OpenID_ServerResponse} provides methods with which you can add
 * data to it which can be signed with the other data in the OpenID
 * signature.
 *
 * For example:
 *
 * <pre>  // when request is a checkid_* request
 *  $response = $request->answer(true);
 *  // this will a signed 'openid.sreg.timezone' parameter to the response
 *  response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
 *
 * Stores
 *
 * The OpenID server needs to maintain state between requests in order
 * to function.  Its mechanism for doing this is called a store.  The
 * store interface is defined in Interface.php.  Additionally, several
 * concrete store implementations are provided, so that most sites
 * won't need to implement a custom store.  For a store backed by flat
 * files on disk, see {@link Auth_OpenID_FileStore}.  For stores based
 * on MySQL, SQLite, or PostgreSQL, see the {@link
 * Auth_OpenID_SQLStore} subclasses.
 *
 * Upgrading
 *
 * The keys by which a server looks up associations in its store have
 * changed in version 1.2 of this library.  If your store has entries
 * created from version 1.0 code, you should empty it.
 *
 * 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
 */

/**
 * Required imports
 */
require_once "Auth/OpenID.php";
require_once "Auth/OpenID/Association.php";
require_once "Auth/OpenID/CryptUtil.php";
require_once "Auth/OpenID/BigMath.php";
require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/KVForm.php";
require_once "Auth/OpenID/TrustRoot.php";
require_once "Auth/OpenID/ServerRequest.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/Nonce.php";

define('AUTH_OPENID_HTTP_OK', 200);
define('AUTH_OPENID_HTTP_REDIRECT', 302);
define('AUTH_OPENID_HTTP_ERROR', 400);

/**
 * @access private
 */
global $_Auth_OpenID_Request_Modes;
$_Auth_OpenID_Request_Modes = array('checkid_setup',
                                    'checkid_immediate');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_URL', 'URL/redirect');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');

/**
 * @access private
 */
function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
{
    return is_a($obj, $cls);
}

/**
 * An error class which gets instantiated and returned whenever an
 * OpenID protocol error occurs.  Be prepared to use this in place of
 * an ordinary server response.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerError {
    /**
     * @access private
     */
    function Auth_OpenID_ServerError($message = null, $text = null,
                                     $reference = null, $contact = null)
    {
        $this->message = $message;
        $this->text = $text;
        $this->contact = $contact;
        $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()
    {
        return $this->getReturnTo() !== null;
    }

    /**
     * Encodes this error's response as a URL suitable for
     * redirection.  If the response has no return_to, another
     * Auth_OpenID_ServerError is returned.
     */
    function encodeToURL()
    {
        if (!$this->message) {
            return null;
        }

        $msg = $this->toMessage();
        return $msg->toURL($this->getReturnTo());
    }

    /**
     * Encodes the response to key-value form.  This is a
     * machine-readable format used to respond to messages which came
     * directly from the consumer and not through the user-agent.  See
     * the OpenID specification.
     */
    function encodeToKVForm()
    {
        return Auth_OpenID_KVForm::fromArray(
                                      array('mode' => 'error',
                                            '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,
        // after encoding.
        $namespace = $this->message->getOpenIDNamespace();
        $reply = new Auth_OpenID_Message($namespace);
        $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
        $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());

        if ($this->contact !== null) {
            $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
        }

        if ($this->reference !== null) {
            $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
                           $this->reference);
        }

        return $reply;
    }

    /**
     * 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_Request_Modes;

        if ($this->hasReturnTo()) {
            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) {
            return null;
        }

        $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
                                       'mode');

        if ($mode) {
            if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
                return Auth_OpenID_ENCODE_KVFORM;
            }
        }
        return null;
    }

    /**
     * Returns this error message.
     */
    function toString()
    {
        if ($this->text) {
            return $this->text;
        } else {
            return get_class($this) . " error";
        }
    }
}

/**
 * Error returned by the server code when a return_to is absent from a
 * request.
 *
 * @package OpenID
 */
class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
    function Auth_OpenID_NoReturnToError($message = null,
                                         $text = "No return_to URL available")
    {
        parent::Auth_OpenID_ServerError($message, $text);
    }

    function toString()
    {
        return "No return_to available";
    }
}

/**
 * An error indicating that the return_to URL is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
    function Auth_OpenID_MalformedReturnURL($message, $return_to)
    {
        $this->return_to = $return_to;
        parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
    }
}

/**
 * This error is returned when the trust_root value is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
    function Auth_OpenID_MalformedTrustRoot($message = null,
                                            $text = "Malformed trust root")
    {
        parent::Auth_OpenID_ServerError($message, $text);
    }

    function toString()
    {
        return "Malformed trust root";
    }
}

/**
 * The base class for all server request classes.
 *
 * @package OpenID
 */
class Auth_OpenID_Request {
    var $mode = null;
}

/**
 * A request to verify the validity of a previous response.
 *
 * @package OpenID
 */
class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
    var $mode = "check_authentication";
    var $invalidate_handle = null;

    function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
                                          $invalidate_handle = null)
    {
        $this->assoc_handle = $assoc_handle;
        $this->signed = $signed;
        if ($invalidate_handle !== null) {
            $this->invalidate_handle = $invalidate_handle;
        }
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->message = null;
    }

    static function fromMessage($message, $server=null)
    {
        $required_keys = array('assoc_handle', 'sig', 'signed');

        foreach ($required_keys as $k) {
            if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
                return new Auth_OpenID_ServerError($message,
                    sprintf("%s request missing required parameter %s from \
                            query", "check_authentication", $k));
            }
        }

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

        $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
        $signed_list = explode(",", $signed_list);

        $signed = $message;
        if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
            $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
        }

        $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
        $result->message = $message;
        $result->sig = $sig;
        $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
                                                      'invalidate_handle');
        return $result;
    }

    function answer($signatory)
    {
        $is_valid = $signatory->verify($this->assoc_handle, $this->signed);

        // Now invalidate that assoc_handle so it this checkAuth
        // message cannot be replayed.
        $signatory->invalidate($this->assoc_handle, true);
        $response = new Auth_OpenID_ServerResponse($this);

        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'is_valid',
                                  ($is_valid ? "true" : "false"));

        if ($this->invalidate_handle) {
            $assoc = $signatory->getAssociation($this->invalidate_handle,
                                                false);
            if (!$assoc) {
                $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                          'invalidate_handle',
                                          $this->invalidate_handle);
            }
        }
        return $response;
    }
}

/**
 * A class implementing plaintext server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_PlainTextServerSession {
    /**
     * An object that knows how to handle association requests with no
     * session type.
     */
    var $session_type = 'no-encryption';
    var $needs_math = false;
    var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');

    static function fromMessage($unused_request)
    {
        return new Auth_OpenID_PlainTextServerSession();
    }

    function answer($secret)
    {
        return array('mac_key' => base64_encode($secret));
    }
}

/**
 * A class implementing DH-SHA1 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA1ServerSession {
    /**
     * An object that knows how to handle association requests with
     * the Diffie-Hellman session type.
     */

    var $session_type = 'DH-SHA1';
    var $needs_math = true;
    var $allowed_assoc_types = array('HMAC-SHA1');
    var $hash_func = 'Auth_OpenID_SHA1';

    function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
    {
        $this->dh = $dh;
        $this->consumer_pubkey = $consumer_pubkey;
    }

    static function getDH($message)
    {
        $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
        $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');

        if ((($dh_modulus === null) && ($dh_gen !== null)) ||
            (($dh_gen === null) && ($dh_modulus !== null))) {

            if ($dh_modulus === null) {
                $missing = 'modulus';
            } else {
                $missing = 'generator';
            }

            return new Auth_OpenID_ServerError($message,
                                'If non-default modulus or generator is '.
                                'supplied, both must be supplied.  Missing '.
                                $missing);
        }

        $lib = Auth_OpenID_getMathLib();

        if ($dh_modulus || $dh_gen) {
            $dh_modulus = $lib->base64ToLong($dh_modulus);
            $dh_gen = $lib->base64ToLong($dh_gen);
            if ($lib->cmp($dh_modulus, 0) == 0 ||
                $lib->cmp($dh_gen, 0) == 0) {
                return new Auth_OpenID_ServerError(
                  $message, "Failed to parse dh_mod or dh_gen");
            }
            $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
        } else {
            $dh = new Auth_OpenID_DiffieHellman();
        }

        $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
                                            'dh_consumer_public');
        if ($consumer_pubkey === null) {
            return new Auth_OpenID_ServerError($message,
                                  'Public key for DH-SHA1 session '.
                                  'not found in query');
        }

        $consumer_pubkey =
            $lib->base64ToLong($consumer_pubkey);

        if ($consumer_pubkey === false) {
            return new Auth_OpenID_ServerError($message,
                                       "dh_consumer_public is not base64");
        }

        return array($dh, $consumer_pubkey);
    }

    static function fromMessage($message)
    {
        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);

        if (is_a($result, 'Auth_OpenID_ServerError')) {
            return $result;
        } else {
            list($dh, $consumer_pubkey) = $result;
            return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
                                                    $consumer_pubkey);
        }
    }

    function answer($secret)
    {
        $lib = Auth_OpenID_getMathLib();
        $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
                                        $this->hash_func);
        return array(
           'dh_server_public' =>
                $lib->longToBase64($this->dh->public),
           'enc_mac_key' => base64_encode($mac_key));
    }
}

/**
 * A class implementing DH-SHA256 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA256ServerSession
      extends Auth_OpenID_DiffieHellmanSHA1ServerSession {

    var $session_type = 'DH-SHA256';
    var $hash_func = 'Auth_OpenID_SHA256';
    var $allowed_assoc_types = array('HMAC-SHA256');

    static function fromMessage($message)
    {
        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);

        if (is_a($result, 'Auth_OpenID_ServerError')) {
            return $result;
        } else {
            list($dh, $consumer_pubkey) = $result;
            return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
                                                      $consumer_pubkey);
        }
    }
}

/**
 * A request to associate with the server.
 *
 * @package OpenID
 */
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
    var $mode = "associate";

    static function getSessionClasses()
    {
        return array(
          'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
          'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
          'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
    }

    function Auth_OpenID_AssociateRequest($session, $assoc_type)
    {
        $this->session = $session;
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->assoc_type = $assoc_type;
    }

    static function fromMessage($message, $server=null)
    {
        if ($message->isOpenID1()) {
            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                             'session_type');

            if ($session_type == 'no-encryption') {
                // oidutil.log('Received OpenID 1 request with a no-encryption '
                //             'assocaition session type. Continuing anyway.')
            } else if (!$session_type) {
                $session_type = 'no-encryption';
            }
        } else {
            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                             'session_type');
            if ($session_type === null) {
                return new Auth_OpenID_ServerError($message,
                  "session_type missing from request");
            }
        }

        $session_class = Auth_OpenID::arrayGet(
           Auth_OpenID_AssociateRequest::getSessionClasses(),
           $session_type);

        if ($session_class === null) {
            return new Auth_OpenID_ServerError($message,
                                               "Unknown session type " .
                                               $session_type);
        }

        $session = call_user_func(array($session_class, 'fromMessage'),
                                  $message);
        if (is_a($session, 'Auth_OpenID_ServerError')) {
            return $session;
        }

        $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                       'assoc_type', 'HMAC-SHA1');

        if (!in_array($assoc_type, $session->allowed_assoc_types)) {
            $fmt = "Session type %s does not support association type %s";
            return new Auth_OpenID_ServerError($message,
              sprintf($fmt, $session_type, $assoc_type));
        }

        $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
        $obj->message = $message;
        $obj->namespace = $message->getOpenIDNamespace();
        return $obj;
    }

    function answer($assoc)
    {
        $response = new Auth_OpenID_ServerResponse($this);
        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
           array(
                 'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
                 'assoc_type' => $this->assoc_type,
                 'assoc_handle' => $assoc->handle));

        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
           $this->session->answer($assoc->secret));

        if (! ($this->session->session_type == 'no-encryption'
               && $this->message->isOpenID1())) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'session_type',
                                      $this->session->session_type);
        }

        return $response;
    }

    function answerUnsupported($text_message,
                               $preferred_association_type=null,
                               $preferred_session_type=null)
    {
        if ($this->message->isOpenID1()) {
            return new Auth_OpenID_ServerError($this->message);
        }

        $response = new Auth_OpenID_ServerResponse($this);
        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'error_code', 'unsupported-type');
        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'error', $text_message);

        if ($preferred_association_type) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'assoc_type',
                                      $preferred_association_type);
        }

        if ($preferred_session_type) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'session_type',
                                      $preferred_session_type);
        }
        $response->code = AUTH_OPENID_HTTP_ERROR;
        return $response;
    }
}

/**
 * A request to confirm the identity of a user.
 *
 * @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.
     */
    var $mode = "checkid_setup"; // or "checkid_immediate"

    /**
     * Whether this request is for immediate mode.
     */
    var $immediate = false;

    /**
     * The trust_root value for this request.
     */
    var $trust_root = null;

    /**
     * The OpenID namespace for this request.
     * deprecated since version 2.0.2
     */
    var $namespace;

    static function make($message, $identity, $return_to, $trust_root = null,
                  $immediate = false, $assoc_handle = null, $server = null)
    {
        if ($server === null) {
            return new Auth_OpenID_ServerError($message,
                                               "server must not be null");
        }

        if ($return_to &&
            !Auth_OpenID_TrustRoot::_parse($return_to)) {
            return new Auth_OpenID_MalformedReturnURL($message, $return_to);
        }

        $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
                                            $trust_root, $immediate,
                                            $assoc_handle, $server);

        $r->namespace = $message->getOpenIDNamespace();
        $r->message = $message;

        if (!$r->trustRootValid()) {
            return new Auth_OpenID_UntrustedReturnURL($message,
                                                      $return_to,
                                                      $trust_root);
        } else {
            return $r;
        }
    }

    function Auth_OpenID_CheckIDRequest($identity, $return_to,
                                        $trust_root = null, $immediate = false,
                                        $assoc_handle = null, $server = null,
                                        $claimed_id = null)
    {
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->assoc_handle = $assoc_handle;
        $this->identity = $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;

        if ($immediate) {
            $this->immediate = true;
            $this->mode = "checkid_immediate";
        } else {
            $this->immediate = false;
            $this->mode = "checkid_setup";
        }
    }

    function equals($other)
    {
        return (
                (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
                ($this->namespace == $other->namespace) &&
                ($this->assoc_handle == $other->assoc_handle) &&
                ($this->identity == $other->identity) &&
                ($this->claimed_id == $other->claimed_id) &&
                ($this->return_to == $other->return_to) &&
                ($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()
    {
        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
        return call_user_func_array($this->verifyReturnTo,
                                    array($this->trust_root, $this->return_to, $fetcher));
    }

    static function fromMessage($message, $server)
    {
        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
        $immediate = null;

        if ($mode == "checkid_immediate") {
            $immediate = true;
            $mode = "checkid_immediate";
        } else {
            $immediate = false;
            $mode = "checkid_setup";
        }

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

        if (($message->isOpenID1()) &&
            (!$return_to)) {
            $fmt = "Missing required field 'return_to' from checkid request";
            return new Auth_OpenID_ServerError($message, $fmt);
        }

        $identity = $message->getArg(Auth_OpenID_OPENID_NS,
                                     'identity');
        $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 {
            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 ($message->isOpenID1()) {
            $trust_root_param = 'trust_root';
        } else {
            $trust_root_param = 'realm';
        }
        $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
                                       $trust_root_param);
        if (! $trust_root) {
            $trust_root = $return_to;
        }

        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,
                                         'assoc_handle');

        $obj = Auth_OpenID_CheckIDRequest::make($message,
                                                $identity,
                                                $return_to,
                                                $trust_root,
                                                $immediate,
                                                $assoc_handle,
                                                $server);

        if (is_a($obj, 'Auth_OpenID_ServerError')) {
            return $obj;
        }

        $obj->claimed_id = $claimed_id;

        return $obj;
    }

    function idSelect()
    {
        // Is the identifier to be selected by the IDP?
        // So IDPs don't have to import the constant
        return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
    }

    function trustRootValid()
    {
        if (!$this->trust_root) {
            return true;
        }

        $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
        if ($tr === false) {
            return new Auth_OpenID_MalformedTrustRoot($this->message,
                                                      $this->trust_root);
        }

        if ($this->return_to !== null) {
            return Auth_OpenID_TrustRoot::match($this->trust_root,
                                                $this->return_to);
        } else {
            return true;
        }
    }

    /**
     * Respond to this request.  Return either an
     * {@link Auth_OpenID_ServerResponse} or
     * {@link Auth_OpenID_ServerError}.
     *
     * @param bool $allow Allow this user to claim this identity, and
     * allow the consumer to have this information?
     *
     * @param string $server_url DEPRECATED.  Passing $op_endpoint to
     * the {@link Auth_OpenID_Server} constructor makes this optional.
     *
     * When an OpenID 1.x immediate mode request does not succeed, it
     * gets back a URL where the request may be carried out in a
     * not-so-immediate fashion.  Pass my URL in here (the fully
     * qualified address of this server's endpoint, i.e.
     * http://example.com/server), and I will use it as a base for the
     * URL for a new request.
     *
     * Optional for requests where {@link $immediate} is false or
     * $allow is true.
     *
     * @param string $identity The OP-local identifier to answer with.
     * Only for use when the relying party requested identifier
     * selection.
     *
     * @param string $claimed_id The claimed identifier to answer
     * with, for use with identifier selection in the case where the
     * claimed identifier and the OP-local identifier differ,
     * i.e. when the claimed_id uses delegation.
     *
     * If $identity is provided but this is not, $claimed_id will
     * default to the value of $identity.  When answering requests
     * that did not ask for identifier selection, the response
     * $claimed_id will default to that of the request.
     *
     * This parameter is new in OpenID 2.0.
     *
     * @return mixed
     */
    function answer($allow, $server_url = null, $identity = null,
                    $claimed_id = null)
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        if (!$server_url) {
            if ((!$this->message->isOpenID1()) &&
                (!$this->server->op_endpoint)) {
                return new Auth_OpenID_ServerError(null,
                  "server should be constructed with op_endpoint to " .
                  "respond to OpenID 2.0 messages.");
            }

            $server_url = $this->server->op_endpoint;
        }

        if ($allow) {
            $mode = 'id_res';
        } else if ($this->message->isOpenID1()) {
            if ($this->immediate) {
                $mode = 'id_res';
            } else {
                $mode = 'cancel';
            }
        } else {
            if ($this->immediate) {
                $mode = 'setup_needed';
            } else {
                $mode = 'cancel';
            }
        }

        if (!$this->trustRootValid()) {
            return new Auth_OpenID_UntrustedReturnURL(null,
                                                      $this->return_to,
                                                      $this->trust_root);
        }

        $response = new Auth_OpenID_ServerResponse($this);

        if ($claimed_id &&
            ($this->message->isOpenID1())) {
            return new Auth_OpenID_ServerError(null,
              "claimed_id is new in OpenID 2.0 and not " .
              "available for ".$this->namespace);
        }

        if ($identity && !$claimed_id) {
            $claimed_id = $identity;
        }

        if ($allow) {

            if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
                if (!$identity) {
                    return new Auth_OpenID_ServerError(null,
                      "This request uses IdP-driven identifier selection.  " .
                      "You must supply an identifier in the response.");
                }

                $response_identity = $identity;
                $response_claimed_id = $claimed_id;

            } else if ($this->identity) {
                if ($identity &&
                    ($this->identity != $identity)) {
                    $fmt = "Request was for %s, cannot reply with identity %s";
                    return new Auth_OpenID_ServerError(null,
                      sprintf($fmt, $this->identity, $identity));
                }

                $response_identity = $this->identity;
                $response_claimed_id = $this->claimed_id;
            } else {
                if ($identity) {
                    return new Auth_OpenID_ServerError(null,
                      "This request specified no identity and " .
                      "you supplied ".$identity);
                }

                $response_identity = null;
            }

            if (($this->message->isOpenID1()) &&
                ($response_identity === null)) {
                return new Auth_OpenID_ServerError(null,
                  "Request was an OpenID 1 request, so response must " .
                  "include an identifier.");
            }

            $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
                   array('mode' => $mode,
                         '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->message->isOpenID2()) {
                    $response->fields->setArg(
                                              Auth_OpenID_OPENID_NS,
                                              'claimed_id',
                                              $response_claimed_id);
                }
            }

        } else {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'mode', $mode);

            if ($this->immediate) {
                if (($this->message->isOpenID1()) &&
                    (!$server_url)) {
                    return new Auth_OpenID_ServerError(null,
                                 'setup_url is required for $allow=false \
                                  in OpenID 1.x immediate mode.');
                }

                $setup_request = new Auth_OpenID_CheckIDRequest(
                                                $this->identity,
                                                $this->return_to,
                                                $this->trust_root,
                                                false,
                                                $this->assoc_handle,
                                                $this->server,
                                                $this->claimed_id);
                $setup_request->message = $this->message;

                $setup_url = $setup_request->encodeToURL($server_url);

                if ($setup_url === null) {
                    return new Auth_OpenID_NoReturnToError();
                }

                $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                          'user_setup_url',
                                          $setup_url);
            }
        }

        return $response;
    }

    function encodeToURL($server_url)
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        // Imported from the alternate reality where these classes are
        // used in both the client and server code, so Requests are
        // Encodable too.  That's right, code imported from alternate
        // realities all for the love of you, id_res/user_setup_url.

        $q = array('mode' => $this->mode,
                   'identity' => $this->identity,
                   'claimed_id' => $this->claimed_id,
                   'return_to' => $this->return_to);

        if ($this->trust_root) {
            if ($this->message->isOpenID1()) {
                $q['trust_root'] = $this->trust_root;
            } else {
                $q['realm'] = $this->trust_root;
            }
        }

        if ($this->assoc_handle) {
            $q['assoc_handle'] = $this->assoc_handle;
        }

        $response = new Auth_OpenID_Message(
            $this->message->getOpenIDNamespace());
        $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
        return $response->toURL($server_url);
    }

    function getCancelURL()
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        if ($this->immediate) {
            return new Auth_OpenID_ServerError(null,
                                               "Cancel is not an appropriate \
                                               response to immediate mode \
                                               requests.");
        }

        $response = new Auth_OpenID_Message(
            $this->message->getOpenIDNamespace());
        $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
        return $response->toURL($this->return_to);
    }
}

/**
 * This class encapsulates the response to an OpenID server request.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerResponse {

    function Auth_OpenID_ServerResponse($request)
    {
        $this->request = $request;
        $this->fields = new Auth_OpenID_Message($this->request->namespace);
    }

    function whichEncoding()
    {
      global $_Auth_OpenID_Request_Modes;

        if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
            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;
        }
    }

    /*
     * 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,
                                     'mode') == 'id_res';
    }

    function encodeToKVForm()
    {
        return $this->fields->toKVForm();
    }
}

/**
 * A web-capable response object which you can use to generate a
 * user-agent response.
 *
 * @package OpenID
 */
class Auth_OpenID_WebResponse {
    var $code = AUTH_OPENID_HTTP_OK;
    var $body = "";

    function Auth_OpenID_WebResponse($code = null, $headers = null,
                                     $body = null)
    {
        if ($code) {
            $this->code = $code;
        }

        if ($headers !== null) {
            $this->headers = $headers;
        } else {
            $this->headers = array();
        }

        if ($body !== null) {
            $this->body = $body;
        }
    }
}

/**
 * Responsible for the signature of query data and the verification of
 * OpenID signature values.
 *
 * @package OpenID
 */
class Auth_OpenID_Signatory {

    // = 14 * 24 * 60 * 60; # 14 days, in seconds
    var $SECRET_LIFETIME = 1209600;

    // keys have a bogus server URL in them because the filestore
    // really does expect that key to be a URL.  This seems a little
    // silly for the server store, since I expect there to be only one
    // server URL.
    var $normal_key = 'http://localhost/|normal';
    var $dumb_key = 'http://localhost/|dumb';

    /**
     * Create a new signatory using a given store.
     */
    function Auth_OpenID_Signatory($store)
    {
        // assert store is not None
        $this->store = $store;
    }

    /**
     * Verify, using a given association handle, a signature with
     * signed key-value pairs from an HTTP request.
     */
    function verify($assoc_handle, $message)
    {
        $assoc = $this->getAssociation($assoc_handle, true);
        if (!$assoc) {
            // oidutil.log("failed to get assoc with handle %r to verify sig %r"
            //             % (assoc_handle, sig))
            return false;
        }

        return $assoc->checkMessageSignature($message);
    }

    /**
     * Given a response, sign the fields in the response's 'signed'
     * list, and insert the signature into the response.
     */
    function sign($response)
    {
        $signed_response = $response;
        $assoc_handle = $response->request->assoc_handle;

        if ($assoc_handle) {
            // normal mode
            $assoc = $this->getAssociation($assoc_handle, false, false);
            if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
                // fall back to dumb mode
                $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
                             'invalidate_handle', $assoc_handle);
                $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');

                if ($assoc && ($assoc->getExpiresIn() <= 0)) {
                    $this->invalidate($assoc_handle, false);
                }

                $assoc = $this->createAssociation(true, $assoc_type);
            }
        } else {
            // dumb mode.
            $assoc = $this->createAssociation(true);
        }

        $signed_response->fields = $assoc->signMessage(
                                      $signed_response->fields);
        return $signed_response;
    }

    /**
     * Make a new association.
     */
    function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
    {
        $secret = Auth_OpenID_CryptUtil::getBytes(
                    Auth_OpenID_getSecretSize($assoc_type));

        $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
        $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);

        $assoc = Auth_OpenID_Association::fromExpiresIn(
                      $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);

        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }

        $this->store->storeAssociation($key, $assoc);
        return $assoc;
    }

    /**
     * Given an association handle, get the association from the
     * store, or return a ServerError or null if something goes wrong.
     */
    function getAssociation($assoc_handle, $dumb, $check_expiration=true)
    {
        if ($assoc_handle === null) {
            return new Auth_OpenID_ServerError(null,
                                     "assoc_handle must not be null");
        }

        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }

        $assoc = $this->store->getAssociation($key, $assoc_handle);

        if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
            if ($check_expiration) {
                $this->store->removeAssociation($key, $assoc_handle);
                $assoc = null;
            }
        }

        return $assoc;
    }

    /**
     * Invalidate a given association handle.
     */
    function invalidate($assoc_handle, $dumb)
    {
        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }
        $this->store->removeAssociation($key, $assoc_handle);
    }
}

/**
 * Encode an {@link Auth_OpenID_ServerResponse} to an
 * {@link Auth_OpenID_WebResponse}.
 *
 * @package OpenID
 */
class Auth_OpenID_Encoder {

    var $responseFactory = 'Auth_OpenID_WebResponse';

    /**
     * Encode an {@link Auth_OpenID_ServerResponse} and return an
     * {@link Auth_OpenID_WebResponse}.
     */
    function encode($response)
    {
        $cls = $this->responseFactory;

        $encode_as = $response->whichEncoding();
        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) {
            $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->toHTML());
        } else {
            return new Auth_OpenID_EncodingError($response);
        }
        /* Allow the response to carry a custom error code (ex: for Association errors) */
        if(isset($response->code)) {
            $wr->code = $response->code;
        }
        return $wr;
    }
}

/**
 * An encoder which also takes care of signing fields when required.
 *
 * @package OpenID
 */
class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {

    function Auth_OpenID_SigningEncoder($signatory)
    {
        $this->signatory = $signatory;
    }

    /**
     * Sign an {@link Auth_OpenID_ServerResponse} and return an
     * {@link Auth_OpenID_WebResponse}.
     */
    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') &&
            $response->needsSigning()) {

            if (!$this->signatory) {
                return new Auth_OpenID_ServerError(null,
                                       "Must have a store to sign request");
            }

            if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
                return new Auth_OpenID_AlreadySigned($response);
            }
            $response = $this->signatory->sign($response);
        }

        return parent::encode($response);
    }
}

/**
 * Decode an incoming query into an Auth_OpenID_Request.
 *
 * @package OpenID
 */
class Auth_OpenID_Decoder {

    function Auth_OpenID_Decoder($server)
    {
        $this->server = $server;

        $this->handlers = array(
            'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
            'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
            'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
            'associate' => 'Auth_OpenID_AssociateRequest'
            );
    }

    /**
     * Given an HTTP query in an array (key-value pairs), decode it
     * into an Auth_OpenID_Request object.
     */
    function decode($query)
    {
        if (!$query) {
            return null;
        }

        $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));

        if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
            return call_user_func_array(array($handlerCls, 'fromMessage'),
                                        array($message, $this->server));
        } else {
            return $handlerCls;
        }
    }

    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("Unrecognized OpenID mode %s", $mode));
    }
}

/**
 * An error that indicates an encoding problem occurred.
 *
 * @package OpenID
 */
class Auth_OpenID_EncodingError {
    function Auth_OpenID_EncodingError($response)
    {
        $this->response = $response;
    }
}

/**
 * An error that indicates that a response was already signed.
 *
 * @package OpenID
 */
class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
    // This response is already signed.
}

/**
 * An error that indicates that the given return_to is not under the
 * given trust_root.
 *
 * @package OpenID
 */
class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
    function Auth_OpenID_UntrustedReturnURL($message, $return_to,
                                            $trust_root)
    {
        parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
        $this->return_to = $return_to;
        $this->trust_root = $trust_root;
    }

    function toString()
    {
        return sprintf("return_to %s not under trust_root %s",
                       $this->return_to, $this->trust_root);
    }
}

/**
 * I handle requests for an OpenID server.
 *
 * Some types of requests (those which are not checkid requests) may
 * be handed to my {@link handleRequest} method, and I will take care
 * of it and return a response.
 *
 * For your convenience, I also provide an interface to {@link
 * Auth_OpenID_Decoder::decode()} and {@link
 * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
 * decodeRequest} and {@link encodeResponse}.
 *
 * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
 *
 * Example:
 *
 * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
 *                                   "http://example.com/op");
 * $request = $oserver->decodeRequest();
 * if (in_array($request->mode, array('checkid_immediate',
 *                                    'checkid_setup'))) {
 *     if ($app->isAuthorized($request->identity, $request->trust_root)) {
 *         $response = $request->answer(true);
 *     } else if ($request->immediate) {
 *         $response = $request->answer(false);
 *     } else {
 *         $app->showDecidePage($request);
 *         return;
 *     }
 * } else {
 *     $response = $oserver->handleRequest($request);
 * }
 *
 * $webresponse = $oserver->encode($response);</pre>
 *
 * @package OpenID
 */
class Auth_OpenID_Server {
    function Auth_OpenID_Server($store, $op_endpoint=null)
    {
        $this->store = $store;
        $this->signatory = new Auth_OpenID_Signatory($this->store);
        $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
        $this->decoder = new Auth_OpenID_Decoder($this);
        $this->op_endpoint = $op_endpoint;
        $this->negotiator = Auth_OpenID_getDefaultNegotiator();
    }

    /**
     * Handle a request.  Given an {@link Auth_OpenID_Request} object,
     * call the appropriate {@link Auth_OpenID_Server} method to
     * process the request and generate a response.
     *
     * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
     * returned by {@link Auth_OpenID_Server::decodeRequest()}.
     *
     * @return Auth_OpenID_ServerResponse $response A response object
     * capable of generating a user-agent reply.
     */
    function handleRequest($request)
    {
        if (method_exists($this, "openid_" . $request->mode)) {
            $handler = array($this, "openid_" . $request->mode);
            return call_user_func_array($handler, array($request));
        }
        return null;
    }

    /**
     * The callback for 'check_authentication' messages.
     */
    function openid_check_authentication($request)
    {
        return $request->answer($this->signatory);
    }

    /**
     * The callback for 'associate' messages.
     */
    function openid_associate($request)
    {
        $assoc_type = $request->assoc_type;
        $session_type = $request->session->session_type;
        if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
            $assoc = $this->signatory->createAssociation(false,
                                                         $assoc_type);
            return $request->answer($assoc);
        } else {
            $message = sprintf('Association type %s is not supported with '.
                               'session type %s', $assoc_type, $session_type);
            list($preferred_assoc_type, $preferred_session_type) =
                $this->negotiator->getAllowedType();
            return $request->answerUnsupported($message,
                                               $preferred_assoc_type,
                                               $preferred_session_type);
        }
    }

    /**
     * Encodes as response in the appropriate format suitable for
     * sending to the user agent.
     */
    function encodeResponse($response)
    {
        return $this->encoder->encode($response);
    }

    /**
     * Decodes a query args array into the appropriate
     * {@link Auth_OpenID_Request} object.
     */
    function decodeRequest($query=null)
    {
        if ($query === null) {
            $query = Auth_OpenID::getQuery();
        }

        return $this->decoder->decode($query);
    }
}


code:
<?php
/**
 * OpenID Server Request
 *
 * @see Auth_OpenID_Server
 *
 * 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
 */

/**
 * Imports
 */
require_once "Auth/OpenID.php";

/**
 * Object that holds the state of a request to the OpenID server
 *
 * With accessor functions to get at the internal request data.
 *
 * @see Auth_OpenID_Server
 * @package OpenID
 */
class Auth_OpenID_ServerRequest {
    function Auth_OpenID_ServerRequest()
    {
        $this->mode = null;
    }
}

repo_name: andremichelle/gnu

license: mit


code:
<?php
/**
 * Functions for dealing with OpenID trust roots
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

require_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.
 *
 * @access private
 */
define('Auth_OpenID___TLDs',
       '/\.(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
     */
    static 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.
     *
     * @static
     *
     * @access private
     *
     * @param string $trust_root The url to parse
     *
     * @return mixed $parsed Either an associative array of trust root
     * parts or false if parsing failed.
     */
    static 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);
        if (array_intersect($keys, $required_parts) != $required_parts) {
            return false;
        }

        if (array_intersect($keys, $forbidden_parts) != array()) {
            return false;
        }

        if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
            return false;
        }

        $scheme = strtolower($parts['scheme']);
        $allowed_schemes = array('http', 'https');
        if (!in_array($scheme, $allowed_schemes)) {
            return false;
        }
        $parts['scheme'] = $scheme;

        $host = strtolower($parts['host']);
        $hostparts = explode('*', $host);
        switch (count($hostparts)) {
        case 1:
            $parts['wildcard'] = false;
            break;
        case 2:
            if ($hostparts[0] ||
                ($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
                return false;
            }
            $host = $hostparts[1];
            $parts['wildcard'] = true;
            break;
        default:
            return false;
        }
        if (strpos($host, ':') !== false) {
            return false;
        }

        $parts['host'] = $host;

        if (isset($parts['path'])) {
            $path = strtolower($parts['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;
    }

    /**
     * Is this trust root sane?
     *
     * A trust root is sane if it is syntactically valid and it has a
     * reasonable domain name. Specifically, the domain name must be
     * more than one level below a standard TLD or more than two
     * levels below a two-letter tld.
     *
     * For example, '*.com' is not a sane trust root, but '*.foo.com'
     * is.  '*.co.uk' is not sane, but '*.bbc.co.uk' is.
     *
     * This check is not always correct, but it attempts to err on the
     * side of marking sane trust roots insane instead of marking
     * insane trust roots sane. For example, 'kink.fm' is marked as
     * insane even though it "should" (for some meaning of should) be
     * marked sane.
     *
     * This function should be used when creating OpenID servers to
     * alert the users of the server when a consumer attempts to get
     * the user to accept a suspicious trust root.
     *
     * @static
     * @param string $trust_root The trust root to check
     * @return bool $sanity Whether the trust root looks OK
     */
    static function isSane($trust_root)
    {
        $parts = Auth_OpenID_TrustRoot::_parse($trust_root);
        if ($parts === false) {
            return false;
        }

        // Localhost is a special case
        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.
        preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
        if (!$matches) {
            return false;
        }
        $tld = $matches[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;
    }

    /**
     * Does this URL match the given trust root?
     *
     * Return whether the URL falls under the given trust root. This
     * does not check whether the trust root is sane. If the URL or
     * trust root do not parse, this function will return false.
     *
     * @param string $trust_root The trust root to match against
     *
     * @param string $url The URL to check
     *
     * @return bool $matches Whether the URL matches against the
     * trust root
     */
    static function match($trust_root, $url)
    {
        $trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
        $url_parsed = Auth_OpenID_TrustRoot::_parse($url);
        if (!$trust_root_parsed || !$url_parsed) {
            return false;
        }

        // Check hosts matching
        if ($url_parsed['wildcard']) {
            return false;
        }
        if ($trust_root_parsed['wildcard']) {
            $host_tail = $trust_root_parsed['host'];
            $host = $url_parsed['host'];
            if ($host_tail &&
                substr($host, -(strlen($host_tail))) != $host_tail &&
                substr($host_tail, 1) != $host) {
                return false;
            }
        } else {
            if ($trust_root_parsed['host'] != $url_parsed['host']) {
                return false;
            }
        }

        // Check path and query matching
        $base_path = $trust_root_parsed['path'];
        $path = $url_parsed['path'];
        if (!isset($trust_root_parsed['query'])) {
            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'];
            $query = @$url_parsed['query'];
            $qplus = substr($query, 0, strlen($base_query) + 1);
            $bqplus = $base_query . '&';
            if ($base_path != $path ||
                ($base_query != $query && $qplus != $bqplus)) {
                return false;
            }
        }

        // The port and scheme need to match exactly
        return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
                $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', 'consumerFromXRDS');

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


code:
<?php

/**
 * URI normalization routines.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

require_once 'Auth/Yadis/Misc.php';

// from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt)
function Auth_OpenID_getURIPattern()
{
    return '&^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?&';
}

function Auth_OpenID_getAuthorityPattern()
{
    return '/^([^@]*@)?([^:]*)(:.*)?/';
}

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();
    for ($i = 0; $i < 256; $i++) {
        $_unreserved[$i] = false;
    }

    for ($i = ord('A'); $i <= ord('Z'); $i++) {
        $_unreserved[$i] = true;
    }

    for ($i = ord('0'); $i <= ord('9'); $i++) {
        $_unreserved[$i] = true;
    }

    for ($i = ord('a'); $i <= ord('z'); $i++) {
        $_unreserved[$i] = true;
    }

    $_unreserved[ord('-')] = true;
    $_unreserved[ord('.')] = true;
    $_unreserved[ord('_')] = true;
    $_unreserved[ord('~')] = true;

    return $_unreserved;
}

function Auth_OpenID_getEscapeRE()
{
    $parts = array();
    foreach (array_merge(Auth_Yadis_getUCSChars(),
                         Auth_Yadis_getIPrivateChars()) as $pair) {
        list($m, $n) = $pair;
        $parts[] = sprintf("%s-%s", chr($m), chr($n));
    }

    return sprintf('[%s]', implode('', $parts));
}

function Auth_OpenID_pct_encoded_replace_unreserved($mo)
{
    $_unreserved = Auth_OpenID_getUnreserved();

    $i = intval($mo[1], 16);
    if ($_unreserved[$i]) {
        return chr($i);
    } else {
        return strtoupper($mo[0]);
    }

    return $mo[0];
}

function Auth_OpenID_pct_encoded_replace($mo)
{
    $code = intval($mo[1], 16);

    // Prevent request splitting by ignoring newline and space characters
    if($code === 0xA || $code === 0xD || $code === ord(' '))
    {
        return $mo[0];
    }
    else
    {
        return chr($code);
    }
}

function Auth_OpenID_remove_dot_segments($path)
{
    $result_segments = array();

    while ($path) {
        if (Auth_Yadis_startswith($path, '../')) {
            $path = substr($path, 3);
        } else if (Auth_Yadis_startswith($path, './')) {
            $path = substr($path, 2);
        } else if (Auth_Yadis_startswith($path, '/./')) {
            $path = substr($path, 2);
        } else if ($path == '/.') {
            $path = '/';
        } else if (Auth_Yadis_startswith($path, '/../')) {
            $path = substr($path, 3);
            if ($result_segments) {
                array_pop($result_segments);
            }
        } else if ($path == '/..') {
            $path = '/';
            if ($result_segments) {
                array_pop($result_segments);
            }
        } else if (($path == '..') ||
                   ($path == '.')) {
            $path = '';
        } else {
            $i = 0;
            if ($path[0] == '/') {
                $i = 1;
            }
            $i = strpos($path, '/', $i);
            if ($i === false) {
                $i = strlen($path);
            }
            $result_segments[] = substr($path, 0, $i);
            $path = substr($path, $i);
        }
    }

    return implode('', $result_segments);
}

function Auth_OpenID_urinorm($uri)
{
    $uri_matches = array();
    preg_match(Auth_OpenID_getURIPattern(), $uri, $uri_matches);

    if (count($uri_matches) < 9) {
        for ($i = count($uri_matches); $i <= 9; $i++) {
            $uri_matches[] = '';
        }
    }

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

    $scheme = $uri_matches[2];
    if ($scheme === '') {
        // No scheme specified
        return null;
    }

    $scheme = strtolower($scheme);
    if (!in_array($scheme, array('http', 'https'))) {
        // Not an absolute HTTP or HTTPS URI
        return null;
    }

    $authority = $uri_matches[4];
    if ($authority === '') {
        // Not an absolute URI
        return null;
    }

    $authority_matches = array();
    preg_match(Auth_OpenID_getAuthorityPattern(),
               $authority, $authority_matches);
    if (count($authority_matches) === 0) {
        // URI does not have a valid authority
        return null;
    }

    if (count($authority_matches) < 4) {
        for ($i = count($authority_matches); $i <= 4; $i++) {
            $authority_matches[] = '';
        }
    }

    list($_whole, $userinfo, $host, $port) = $authority_matches;

    if ($userinfo === null) {
        $userinfo = '';
    }

    if (strpos($host, '%') !== -1) {
        $host = strtolower($host);
        $host = preg_replace_callback(
                  Auth_OpenID_getEncodedPattern(),
                  'Auth_OpenID_pct_encoded_replace', $host);
        // NO IDNA.
        // $host = unicode($host, 'utf-8').encode('idna');
    } else {
        $host = strtolower($host);
    }

    if ($port) {
        if (($port == ':') ||
            ($scheme == 'http' && $port == ':80') ||
            ($scheme == 'https' && $port == ':443')) {
            $port = '';
        }
    } else {
        $port = '';
    }

    $authority = $userinfo . $host . $port;

    $path = $uri_matches[5];
    $path = preg_replace_callback(
               Auth_OpenID_getEncodedPattern(),
               'Auth_OpenID_pct_encoded_replace_unreserved', $path);

    $path = Auth_OpenID_remove_dot_segments($path);
    if (!$path) {
        $path = '/';
    }

    $query = $uri_matches[6];
    if ($query === null) {
        $query = '';
    }

    $fragment = $uri_matches[8];
    if ($fragment === null) {
        $fragment = '';
    }

    return $scheme . '://' . $authority . $path . $query . $fragment;
}


code:
<?php

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

/**
 * Require logging functionality
 */
require_once "Auth/OpenID.php";

define('Auth_OpenID_FETCHER_MAX_RESPONSE_KB', 1024);
define('Auth_OpenID_USER_AGENT',
       'php-openid/'.Auth_OpenID_VERSION.' (php/'.phpversion().')');

class Auth_Yadis_HTTPResponse {
    function Auth_Yadis_HTTPResponse($final_url = null, $status = null,
                                         $headers = null, $body = null)
    {
        $this->final_url = $final_url;
        $this->status = $status;
        $this->headers = $headers;
        $this->body = $body;
    }
}

/**
 * This class is the interface for HTTP fetchers the Yadis library
 * uses.  This interface is only important if you need to write a new
 * fetcher for some reason.
 *
 * @access private
 * @package OpenID
 */
class Auth_Yadis_HTTPFetcher {

    var $timeout = 20; // timeout in seconds.

    /**
     * Return whether a URL can be fetched.  Returns false if the URL
     * scheme is not allowed or is not supported by this fetcher
     * implementation; returns true otherwise.
     *
     * @return bool
     */
    function canFetchURL($url)
    {
        if ($this->isHTTPS($url) && !$this->supportsSSL()) {
            Auth_OpenID::log("HTTPS URL unsupported fetching %s",
                             $url);
            return false;
        }

        if (!$this->allowedURL($url)) {
            Auth_OpenID::log("URL fetching not allowed for '%s'",
                             $url);
            return false;
        }

        return true;
    }

    /**
     * Return whether a URL should be allowed. Override this method to
     * conform to your local policy.
     *
     * By default, will attempt to fetch any http or https URL.
     */
    function allowedURL($url)
    {
        return $this->URLHasAllowedScheme($url);
    }

    /**
     * Does this fetcher implementation (and runtime) support fetching
     * HTTPS URLs?  May inspect the runtime environment.
     *
     * @return bool $support True if this fetcher supports HTTPS
     * fetching; false if not.
     */
    function supportsSSL()
    {
        trigger_error("not implemented", E_USER_ERROR);
    }

    /**
     * Is this an https URL?
     *
     * @access private
     */
    function isHTTPS($url)
    {
        return (bool)preg_match('/^https:\/\//i', $url);
    }

    /**
     * Is this an http or https URL?
     *
     * @access private
     */
    function URLHasAllowedScheme($url)
    {
        return (bool)preg_match('/^https?:\/\//i', $url);
    }

    /**
     * @access private
     */
    function _findRedirect($headers, $url)
    {
        foreach ($headers as $line) {
            if (strpos(strtolower($line), "location: ") === 0) {
                $parts = explode(" ", $line, 2);
                $loc = $parts[1];
                $ppos = strpos($loc, "://");
                if ($ppos === false || $ppos > strpos($loc, "/")) {
                  /* no host; add it */
                  $hpos = strpos($url, "://");
                  $prt = substr($url, 0, $hpos+3);
                  $url = substr($url, $hpos+3);
                  if (substr($loc, 0, 1) == "/") {
                    /* absolute path */
                    $fspos = strpos($url, "/");
                    if ($fspos) $loc = $prt.substr($url, 0, $fspos).$loc;
                    else $loc = $prt.$url.$loc;
                  } else {
                    /* relative path */
                    $pp = $prt;
                    while (1) {
                      $xpos = strpos($url, "/");
                      if ($xpos === false) break;
                      $apos = strpos($url, "?");
                      if ($apos !== false && $apos < $xpos) break;
                      $apos = strpos($url, "&");
                      if ($apos !== false && $apos < $xpos) break;
                      $pp .= substr($url, 0, $xpos+1);
                      $url = substr($url, $xpos+1);
                    }
                    $loc = $pp.$loc;
                  }
                }
                return $loc;
            }
        }
        return null;
    }

    /**
     * Fetches the specified URL using optional extra headers and
     * returns the server's response.
     *
     * @param string $url The URL to be fetched.
     * @param array $extra_headers An array of header strings
     * (e.g. "Accept: text/html").
     * @return mixed $result An array of ($code, $url, $headers,
     * $body) if the URL could be fetched; null if the URL does not
     * pass the URLHasAllowedScheme check or if the server's response
     * is malformed.
     */
    function get($url, $headers = null)
    {
        trigger_error("not implemented", E_USER_ERROR);
    }
}


code:
<?php

/**
 * Yadis service manager to be used during yadis-driven authentication
 * attempts.
 *
 * @package OpenID
 */

/**
 * The base session class used by the Auth_Yadis_Manager.  This
 * class wraps the default PHP session machinery and should be
 * subclassed if your application doesn't use PHP sessioning.
 *
 * @package OpenID
 */
class Auth_Yadis_PHPSession {
    /**
     * Set a session key/value pair.
     *
     * @param string $name The name of the session key to add.
     * @param string $value The value to add to the session.
     */
    function set($name, $value)
    {
        $_SESSION[$name] = $value;
    }

    /**
     * Get a key's value from the session.
     *
     * @param string $name The name of the key to retrieve.
     * @param string $default The optional value to return if the key
     * is not found in the session.
     * @return string $result The key's value in the session or
     * $default if it isn't found.
     */
    function get($name, $default=null)
    {
        if (isset($_SESSION) && array_key_exists($name, $_SESSION)) {
            return $_SESSION[$name];
        } else {
            return $default;
        }
    }

    /**
     * Remove a key/value pair from the session.
     *
     * @param string $name The name of the key to remove.
     */
    function del($name)
    {
        unset($_SESSION[$name]);
    }

    /**
     * Return the contents of the session in array form.
     */
    function contents()
    {
        return $_SESSION;
    }
}

/**
 * A session helper class designed to translate between arrays and
 * objects.  Note that the class used must have a constructor that
 * takes no parameters.  This is not a general solution, but it works
 * for dumb objects that just need to have attributes set.  The idea
 * is that you'll subclass this and override $this->check($data) ->
 * bool to implement your own session data validation.
 *
 * @package OpenID
 */
class Auth_Yadis_SessionLoader {
    /**
     * Override this.
     *
     * @access private
     */
    function check($data)
    {
        return true;
    }

    /**
     * Given a session data value (an array), this creates an object
     * (returned by $this->newObject()) whose attributes and values
     * are those in $data.  Returns null if $data lacks keys found in
     * $this->requiredKeys().  Returns null if $this->check($data)
     * evaluates to false.  Returns null if $this->newObject()
     * evaluates to false.
     *
     * @access private
     */
    function fromSession($data)
    {
        if (!$data) {
            return null;
        }

        $required = $this->requiredKeys();

        foreach ($required as $k) {
            if (!array_key_exists($k, $data)) {
                return null;
            }
        }

        if (!$this->check($data)) {
            return null;
        }

        $data = array_merge($data, $this->prepareForLoad($data));
        $obj = $this->newObject($data);

        if (!$obj) {
            return null;
        }

        foreach ($required as $k) {
            $obj->$k = $data[$k];
        }

        return $obj;
    }

    /**
     * Prepares the data array by making any necessary changes.
     * Returns an array whose keys and values will be used to update
     * the original data array before calling $this->newObject($data).
     *
     * @access private
     */
    function prepareForLoad($data)
    {
        return array();
    }

    /**
     * Returns a new instance of this loader's class, using the
     * session data to construct it if necessary.  The object need
     * only be created; $this->fromSession() will take care of setting
     * the object's attributes.
     *
     * @access private
     */
    function newObject($data)
    {
        return null;
    }

    /**
     * Returns an array of keys and values built from the attributes
     * of $obj.  If $this->prepareForSave($obj) returns an array, its keys
     * and values are used to update the $data array of attributes
     * from $obj.
     *
     * @access private
     */
    function toSession($obj)
    {
        $data = array();
        foreach ($obj as $k => $v) {
            $data[$k] = $v;
        }

        $extra = $this->prepareForSave($obj);

        if ($extra && is_array($extra)) {
            foreach ($extra as $k => $v) {
                $data[$k] = $v;
            }
        }

        return $data;
    }

    /**
     * Override this.
     *
     * @access private
     */
    function prepareForSave($obj)
    {
        return array();
    }
}

/**
 * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
 *
 * @package OpenID
 */
class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
    function newObject($data)
    {
        return new Auth_OpenID_ServiceEndpoint();
    }

    function requiredKeys()
    {
        $obj = new Auth_OpenID_ServiceEndpoint();
        $data = array();
        foreach ($obj as $k => $v) {
            $data[] = $k;
        }
        return $data;
    }

    function check($data)
    {
        return is_array($data['type_uris']);
    }
}

/**
 * A concrete loader implementation for Auth_Yadis_Managers.
 *
 * @package OpenID
 */
class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
    function requiredKeys()
    {
        return array('starting_url',
                     'yadis_url',
                     'services',
                     'session_key',
                     '_current',
                     'stale');
    }

    function newObject($data)
    {
        return new Auth_Yadis_Manager($data['starting_url'],
                                          $data['yadis_url'],
                                          $data['services'],
                                          $data['session_key']);
    }

    function check($data)
    {
        return is_array($data['services']);
    }

    function prepareForLoad($data)
    {
        $loader = new Auth_OpenID_ServiceEndpointLoader();
        $services = array();
        foreach ($data['services'] as $s) {
            $services[] = $loader->fromSession($s);
        }
        return array('services' => $services);
    }

    function prepareForSave($obj)
    {
        $loader = new Auth_OpenID_ServiceEndpointLoader();
        $services = array();
        foreach ($obj->services as $s) {
            $services[] = $loader->toSession($s);
        }
        return array('services' => $services);
    }
}

/**
 * The Yadis service manager which stores state in a session and
 * iterates over <Service> elements in a Yadis XRDS document and lets
 * a caller attempt to use each one.  This is used by the Yadis
 * library internally.
 *
 * @package OpenID
 */
class Auth_Yadis_Manager {

    /**
     * Intialize a new yadis service manager.
     *
     * @access private
     */
    function Auth_Yadis_Manager($starting_url, $yadis_url,
                                    $services, $session_key)
    {
        // The URL that was used to initiate the Yadis protocol
        $this->starting_url = $starting_url;

        // The URL after following redirects (the identifier)
        $this->yadis_url = $yadis_url;

        // List of service elements
        $this->services = $services;

        $this->session_key = $session_key;

        // Reference to the current service object
        $this->_current = null;

        // Stale flag for cleanup if PHP lib has trouble.
        $this->stale = false;
    }

    /**
     * @access private
     */
    function length()
    {
        // How many untried services remain?
        return count($this->services);
    }

    /**
     * Return the next service
     *
     * $this->current() will continue to return that service until the
     * next call to this method.
     */
    function nextService()
    {

        if ($this->services) {
            $this->_current = array_shift($this->services);
        } else {
            $this->_current = null;
        }

        return $this->_current;
    }

    /**
     * @access private
     */
    function current()
    {
        // Return the current service.
        // Returns None if there are no services left.
        return $this->_current;
    }

    /**
     * @access private
     */
    function forURL($url)
    {
        return in_array($url, array($this->starting_url, $this->yadis_url));
    }

    /**
     * @access private
     */
    function started()
    {
        // Has the first service been returned?
        return $this->_current !== null;
    }
}

/**
 * State management for discovery.
 *
 * High-level usage pattern is to call .getNextService(discover) in
 * order to find the next available service for this user for this
 * session. Once a request completes, call .cleanup() to clean up the
 * session state.
 *
 * @package OpenID
 */
class Auth_Yadis_Discovery {

    /**
     * @access private
     */
    var $DEFAULT_SUFFIX = 'auth';

    /**
     * @access private
     */
    var $PREFIX = '_yadis_services_';

    /**
     * Initialize a discovery object.
     *
     * @param Auth_Yadis_PHPSession $session An object which
     * implements the Auth_Yadis_PHPSession API.
     * @param string $url The URL on which to attempt discovery.
     * @param string $session_key_suffix The optional session key
     * suffix override.
     */
    function Auth_Yadis_Discovery($session, $url,
                                      $session_key_suffix = null)
    {
        /// Initialize a discovery object
        $this->session = $session;
        $this->url = $url;
        if ($session_key_suffix === null) {
            $session_key_suffix = $this->DEFAULT_SUFFIX;
        }

        $this->session_key_suffix = $session_key_suffix;
        $this->session_key = $this->PREFIX . $this->session_key_suffix;
    }

    /**
     * Return the next authentication service for the pair of
     * user_input and session. This function handles fallback.
     */
    function getNextService($discover_cb, $fetcher)
    {
        $manager = $this->getManager();
        if (!$manager || (!$manager->services)) {
            $this->destroyManager();

            list($yadis_url, $services) = call_user_func_array($discover_cb,
                                                               array(
                                                                $this->url,
                                                                $fetcher,
                                                               ));

            $manager = $this->createManager($services, $yadis_url);
        }

        if ($manager) {
            $loader = new Auth_Yadis_ManagerLoader();
            $service = $manager->nextService();
            $this->session->set($this->session_key,
                                serialize($loader->toSession($manager)));
        } else {
            $service = null;
        }

        return $service;
    }

    /**
     * Clean up Yadis-related services in the session and return the
     * most-recently-attempted service from the manager, if one
     * exists.
     *
     * @param $force True if the manager should be deleted regardless
     * of whether it's a manager for $this->url.
     */
    function cleanup($force=false)
    {
        $manager = $this->getManager($force);
        if ($manager) {
            $service = $manager->current();
            $this->destroyManager($force);
        } else {
            $service = null;
        }

        return $service;
    }

    /**
     * @access private
     */
    function getSessionKey()
    {
        // Get the session key for this starting URL and suffix
        return $this->PREFIX . $this->session_key_suffix;
    }

    /**
     * @access private
     *
     * @param $force True if the manager should be returned regardless
     * of whether it's a manager for $this->url.
     */
    function getManager($force=false)
    {
        // Extract the YadisServiceManager for this object's URL and
        // suffix from the session.

        $manager_str = $this->session->get($this->getSessionKey());
        $manager = null;

        if ($manager_str !== null) {
            $loader = new Auth_Yadis_ManagerLoader();
            $manager = $loader->fromSession(unserialize($manager_str));
        }

        if ($manager && ($manager->forURL($this->url) || $force)) {
            return $manager;
        }
    }

    /**
     * @access private
     */
    function createManager($services, $yadis_url = null)
    {
        $key = $this->getSessionKey();
        if ($this->getManager()) {
            return $this->getManager();
        }

        if ($services) {
            $loader = new Auth_Yadis_ManagerLoader();
            $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
                                              $services, $key);
            $this->session->set($this->session_key,
                                serialize($loader->toSession($manager)));
            return $manager;
        }
    }

    /**
     * @access private
     *
     * @param $force True if the manager should be deleted regardless
     * of whether it's a manager for $this->url.
     */
    function destroyManager($force=false)
    {
        if ($this->getManager($force) !== null) {
            $key = $this->getSessionKey();
            $this->session->del($key);
        }
    }
}


code:
<?php

/**
 * Miscellaneous utility values and functions for OpenID and Yadis.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

function Auth_Yadis_getUCSChars()
{
    return array(
                 array(0xA0, 0xD7FF),
                 array(0xF900, 0xFDCF),
                 array(0xFDF0, 0xFFEF),
                 array(0x10000, 0x1FFFD),
                 array(0x20000, 0x2FFFD),
                 array(0x30000, 0x3FFFD),
                 array(0x40000, 0x4FFFD),
                 array(0x50000, 0x5FFFD),
                 array(0x60000, 0x6FFFD),
                 array(0x70000, 0x7FFFD),
                 array(0x80000, 0x8FFFD),
                 array(0x90000, 0x9FFFD),
                 array(0xA0000, 0xAFFFD),
                 array(0xB0000, 0xBFFFD),
                 array(0xC0000, 0xCFFFD),
                 array(0xD0000, 0xDFFFD),
                 array(0xE1000, 0xEFFFD)
                 );
}

function Auth_Yadis_getIPrivateChars()
{
    return array(
                 array(0xE000, 0xF8FF),
                 array(0xF0000, 0xFFFFD),
                 array(0x100000, 0x10FFFD)
                 );
}

function Auth_Yadis_pct_escape_unicode($char_match)
{
    $c = $char_match[0];
    $result = "";
    for ($i = 0; $i < strlen($c); $i++) {
        $result .= "%".sprintf("%X", ord($c[$i]));
    }
    return $result;
}

function Auth_Yadis_startswith($s, $stuff)
{
    return strpos($s, $stuff) === 0;
}