Add simpleid-1.0.5
diff --git a/simpleid/www/common.inc.php b/simpleid/www/common.inc.php
new file mode 100644
index 0000000..b0f3fd6
--- /dev/null
+++ b/simpleid/www/common.inc.php
@@ -0,0 +1,664 @@
+<?php 
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-8
+ *
+ * Includes code Drupal OpenID module (http://drupal.org/project/openid)
+ * Rowan Kerr <rowan@standardinteractive.com>
+ * James Walker <james@bryght.com>
+ *
+ * Copyright (C) Rowan Kerr and James Walker
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ * $Id$
+ */
+
+/**
+ * Common functions used by SimpleID, and the implementation of extensions.
+ *
+ * @package simpleid
+ * @filesource
+ */
+ 
+/**
+ * Sets a message to display to the user on the rendered SimpleID page.
+ *
+ * @param string $msg the message to set
+ */
+function set_message($msg) {
+    global $xtpl;
+    
+    $xtpl->assign('message', $msg);
+    $xtpl->parse('main.message');
+}
+
+/**
+ * Displays a fatal error message and exits.
+ *
+ * @param string $error the message to set
+ */
+function indirect_fatal_error($error) {
+    global $xtpl;
+    
+    set_message($error);
+    
+    $xtpl->parse('main');
+    $xtpl->out('main');
+    exit;
+}
+
+/**
+ * Send a HTTP response code to the user agent.
+ *
+ * The format of the HTTP response code depends on the way PHP is run.
+ * When run as an Apache module, a properly formatted HTTP response
+ * string is sent.  When run via CGI, the response code is sent via the
+ * Status response header.
+ *
+ * @param string $code the response code along
+ */
+function header_response_code($code) {
+    if (substr(PHP_SAPI, 0,3) === 'cgi') {
+        header('Status: ' . $code);
+    } else {
+        header($_SERVER['SERVER_PROTOCOL'] . ' ' . $code);
+    }
+}
+
+/**
+ * Determines whether the current connection with the user agent is via
+ * HTTPS.
+ *
+ * HTTPS is detected if one of the following occurs:
+ *
+ * - $_SERVER['HTTPS'] is set to 'on' (Apache installations)
+ * - $_SERVER['HTTP_X_FORWARDED_PROTO'] is set to 'https' (reverse proxies)
+ * - $_SERVER['HTTP_FRONT_END_HTTPS'] is set to 'on'
+ *
+ * @return bool true if the connection is via HTTPS
+ */
+function is_https() {
+    return (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on'))
+        || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && (strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'))
+        || (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && ($_SERVER['HTTP_FRONT_END_HTTPS'] == 'on'));
+}
+
+
+/**
+ * Ensure the current connection with the user agent is secure with HTTPS.
+ *
+ * This function uses {@link is_https()} to determine whether the connection
+ * is via HTTPS.  If it is, this function will return successfully.
+ *
+ * If it is not, what happens next is determined by the following steps.
+ *
+ * 1. If $allow_override is true and {@link SIMPLEID_ALLOW_PLAINTEXT} is also true,
+ * then the function will return successfully
+ * 2. Otherwise, then it will either redirect (if $action is
+ * redirect) or return an error (if $action is error)
+ *
+ * @param string $action what to do if connection is not secure - either
+ * 'redirect' or 'error'
+ * @param boolean $allow_override whether SIMPLEID_ALLOW_PLAINTEXT is checked
+ * to see if an unencrypted connection is allowed
+ * @param string $redirect_url if $action is redirect, what URL to redirect to.
+ * If null, this will redirect to the same page (albeit with an HTTPS connection)
+ * @param boolean $strict whether HTTP Strict Transport Security is active
+ * @see SIMPLEID_ALLOW_PLAINTEXT
+ */
+function check_https($action = 'redirect', $allow_override = false, $redirect_url = null, $strict = true) {
+    if (is_https()) {
+        if ($strict) header('Strict-Transport-Security: max-age=3600');
+        return;
+    }
+    
+    if ($allow_override && SIMPLEID_ALLOW_PLAINTEXT) return;
+    
+    if ($action == 'error') {
+        if (substr(PHP_SAPI, 0,3) === 'cgi') {
+            header('Status: 426 Upgrade Required');
+        } else {
+            header($_SERVER['SERVER_PROTOCOL'] . ' 426 Upgrade Required');
+        }
+
+        header('Upgrade: TLS/1.2, HTTP/1.1');
+        header('Connection: Upgrade');
+        indirect_fatal_error(t('An encrypted connection (HTTPS) is required for this page.'));
+        return;
+    }
+    
+    if ($redirect_url == null) $redirect_url = simpleid_url('', $_SERVER['QUERY_STRING'], false, 'https');
+    
+    if (substr(PHP_SAPI, 0,3) === 'cgi') {
+        header('Status: 301 Moved Permanently');
+    } else {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently');
+    }
+        
+    header('Location: ' . $redirect_url);
+}
+
+/**
+ * Fix PHP's handling of request data.  PHP changes dots in all request parameters
+ * to underscores when creating the $_GET, $_POST and $_REQUEST arrays.
+ *
+ * This function scans the original query string and POST parameters and fixes
+ * them.
+ */
+function fix_http_request() {
+    // Fix GET parameters
+    if (isset($_SERVER['QUERY_STRING'])) {
+        $get = parse_http_query($_SERVER['QUERY_STRING']);
+        
+        foreach ($get as $key => $value) {
+            // We strip out array-like identifiers - PHP uses special processing for these
+            if ((strpos($key, '[') !== FALSE) && (strpos($key, ']') !== FALSE)) $key = substr($key, 0, strpos($key, '['));
+            
+            // Replace special characters with underscore as per PHP processing
+            $php_key = preg_replace('/[ .[\x80-\x9F]/', '_', $key);
+            
+            // See if the PHP key is present; if so, copy and delete
+            if (($key != $php_key) && isset($_GET[$php_key])) {
+                $_GET[$key] = $_GET[$php_key];
+                $_REQUEST[$key] = $_REQUEST[$php_key];
+                unset($_GET[$php_key]);
+                unset($_REQUEST[$php_key]);
+            }
+        }
+    }
+    
+    // Fix POST parameters
+    $input = file_get_contents('php://input');
+    if ($input !== FALSE) {
+        $post = parse_http_query($input);
+        
+        foreach ($post as $key => $value) {
+            // We strip out array-like identifiers - PHP uses special processing for these
+            if ((strpos($key, '[') !== FALSE) && (strpos($key, ']') !== FALSE)) $key = substr($key, 0, strpos($key, '['));
+            
+            // Replace special characters with underscore as per PHP processing
+            $php_key = preg_replace('/[ .[\x80-\x9F]/', '_', $key);
+            
+            // See if the PHP key is present; if so, copy and delete
+            if (($key != $php_key) && isset($_POST[$php_key])) {
+                $_POST[$key] = $_POST[$php_key];
+                $_REQUEST[$key] = $_REQUEST[$php_key];
+                unset($_POST[$php_key]);
+                unset($_REQUEST[$php_key]);
+            }
+        }
+    }
+}
+
+/**
+ * Parses a query string.
+ *
+ * @param string $query the query string to parse
+ * @return array an array containing the parsed key-value pairs
+ *
+ * @since 0.7
+ */
+function parse_http_query($query) {
+    $data = array();
+    
+    if ($query === NULL) return array();
+    if ($query === '') return array();
+    
+    $pairs = explode('&', $query);
+    
+    foreach ($pairs as $pair) {
+        list ($key, $value) = explode('=', $pair, 2);
+        $data[$key] = urldecode($value);
+    }
+
+    return $data;
+}
+
+/**
+ * Assigns and returns a unique ID for the user agent (UAID).
+ *
+ * A UAID uniquely identifies the user agent (e.g. browser) used to
+ * make the HTTP request.  The UAID is stored in a long-dated
+ * cookie.  Therefore, the UAID may be useful for security purposes.
+ *
+ * This function will look for a cookie sent by the user agent with
+ * the name returned by {@link simpleid_cookie_name()} with a suffix
+ * of uaid.  If the cookie does not exist, it will generate a
+ * random UAID and return it to the user agent with a Set-Cookie
+ * response header.
+ *
+ * @return string the UAID
+ */
+function get_user_agent_id() {
+    if (isset($_COOKIE[simpleid_cookie_name('uaid')])) return $_COOKIE[simpleid_cookie_name('uaid')];
+
+    $uaid = bin2hex(pack('LLLL', mt_rand(), mt_rand(), mt_rand(), mt_rand()));
+    setcookie(simpleid_cookie_name('uaid'), $uaid, time() + 315360000, get_base_path(), '', false, true);
+    return $uaid;
+}
+
+/**
+ * Content type negotiation using the Accept Header.
+ *
+ * Under HTTP, the user agent is able to negoatiate the content type returned with
+ * the server using HTTP Accept header.  This header contains a comma-delimited
+ * list of items (e.g. content types) which the user agent is able to
+ * accept, ranked by a quality parameter.
+ *
+ * This function takes the header from the user agent, compares it against the
+ * content types which the server can provide, then returns the item which the highest
+ * quality which the server can provide.
+ *
+ * @param array $content_types an array of content types which the server can
+ * provide
+ * @param string $accept_header the header string provided by the user agent.
+ * If NULL, this defaults to $_SERVER['HTTP_ACCEPT'] if available
+ * @return string the negotiated content type, FALSE if $accept_header is NULL and
+ * the user agent did not provide an Accept header, or NULL if the negotiation is
+ * unsuccessful
+ *
+ * @since 0.8
+ *
+ */
+function negotiate_content_type($content_types, $accept_header = NULL) {
+    $content_types = array_map("strtolower", $content_types);
+    if (($accept_header == NULL) && isset($_SERVER['HTTP_ACCEPT'])) $accept_header = $_SERVER['HTTP_ACCEPT'];
+    
+    if ($accept_header) {
+        $acceptible = preg_split('/\s*,\s*/', strtolower(trim($accept_header)));
+        for ($i = 0; $i < count($acceptible); $i++) {
+            $split = preg_split('/\s*;\s*q\s*=\s*/', $acceptible[$i], 2);
+            $item = strtolower($split[0]);
+            
+            if (count($split) == 1) {
+                $q = 1.0;
+            } else {
+                $q = doubleval($split[1]);
+            }
+            
+            if ($q > 0.0) {
+                if (in_array($item, $content_types)) {
+                    if ($q == 1.0) {
+                        return $item;
+                    }
+                    $candidates[$item] = $q;
+                } else {
+                    $item = preg_quote($item, '/');
+                    $item = strtr($item, array('\*' => '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'));
+                    
+                    foreach ($content_types as $value) {
+                        if (preg_match("/^$item$/", $value)) {
+                            if ($q == 1.0) {
+                                return $value;
+                            }
+                            $candidates[$value] = $q;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        if (isset($candidates)) {
+            arsort($candidates);
+            reset($candidates);
+            return key($candidates);
+        }
+        return NULL;
+    } else {
+        // No headers
+        return FALSE;
+    }
+}
+
+/**
+ * Serialises a variable for inclusion as a URL parameter.
+ *
+ * @param mixed $data the data to serialise
+ * @return string serialised data
+ * @see unpickle()
+ */
+function pickle($data) {
+    return base64_encode(gzcompress(serialize($data)));
+}
+
+/**
+ * Deserialises data specified in a URL parameter as a variable.
+ *
+ * @param string $pickle the serialised data
+ * @return mixed the deserialised data
+ * @see pickle()
+ */
+function unpickle($pickle) {
+    return unserialize(gzuncompress(base64_decode($pickle)));
+}
+
+/**
+ * Compares two strings using the same time whether they're equal or not.
+ * This function should be used to mitigate timing attacks when, for
+ * example, comparing password hashes
+ *
+ * @param string $str1
+ * @param string $str2
+ * @return bool true if the two strings are equal
+ */
+function secure_compare($str1, $str2) {
+    if (function_exists('hash_equals')) return hash_equals($str1, $str2);
+
+    $xor = $str1 ^ $str2;
+    $result = strlen($str1) ^ strlen($str2); //not the same length, then fail ($result != 0)
+    for ($i = strlen($xor) - 1; $i >= 0; $i--) $result += ord($xor[$i]);
+    return !$result;
+}
+
+/**
+ * Obtains the URI of the current request, given a base URI.
+ *
+ * @param string $base the base URI
+ * @return string the request URI
+ */
+function get_request_uri($base) {
+    $i = strpos($base, '//');
+    $i = strpos($base, '/', $i + 2);
+    
+    if ($i === false) {
+        return $base . $_SERVER['REQUEST_URI'];
+    } else {
+        return substr($base, 0, $i) . $_SERVER['REQUEST_URI'];
+    }
+}
+
+/**
+ * Returns the base URL path, relative to the current host, of the SimpleID
+ * installation.
+ *
+ * This is worked out from {@link SIMPLEID_BASE_URL}.  It will always contain
+ * a trailing slash.
+ *
+ * @return string the base URL path
+ * @since 0.8
+ * @see SIMPLEID_BASE_URL
+ */
+function get_base_path() {
+    static $base_path;
+    
+    if (!$base_path) {
+        if ((substr(SIMPLEID_BASE_URL, -1) == '/') || (substr(SIMPLEID_BASE_URL, -9) == 'index.php')) {
+            $url = SIMPLEID_BASE_URL;
+        } else {
+            $url = SIMPLEID_BASE_URL . '/';
+        }
+        
+        $parts = parse_url($url);
+        $base_path = $parts['path'];
+    }
+    
+    return $base_path;
+}
+
+/**
+ * Determines whether the {@link SIMPLEID_BASE_URL} configuration option is a
+ * HTTPS URL.
+ *
+ * @return true if SIMPLEID_BASE_URL is a HTTPS URL
+ */
+function is_base_https() {
+    return (stripos(SIMPLEID_BASE_URL, 'https:') === 0);
+}
+
+/**
+ * Obtains a SimpleID URL.  URLs produced by SimpleID should use this function.
+ *
+ * @param string $q the q parameter
+ * @param string $params a properly encoded query string
+ * @param bool $relative whether a relative URL should be returned
+ * @param string $secure if $relative is false, either 'https' to force an HTTPS connection, 'http' to force
+ * an unencrypted HTTP connection, 'detect' to base on the current connection, or NULL to vary based on SIMPLEID_BASE_URL
+ * @return string the url
+ *
+ * @since 0.7
+ */
+function simpleid_url($q = '', $params = '', $relative = false, $secure = null) {
+    if ($relative) {
+        $url = get_base_path();
+    } else {
+        // Make sure that the base has a trailing slash
+        if ((substr(SIMPLEID_BASE_URL, -1) == '/') || (substr(SIMPLEID_BASE_URL, -9) == 'index.php')) {
+            $url = SIMPLEID_BASE_URL;
+        } else {
+            $url = SIMPLEID_BASE_URL . '/';
+        }
+        
+        if (($secure == 'https') && (stripos($url, 'http:') === 0)) {
+            $url = 'https:' . substr($url, 5);
+        }
+        if (($secure == 'http') && (stripos($url, 'https:') === 0)) {
+            $url = 'http:' . substr($url, 6);
+        }
+        if (($secure == 'detect') && (is_https()) && (stripos($url, 'http:') === 0)) {
+            $url = 'https:' . substr($url, 5);
+        }
+        if (($secure == 'detect') && (!is_https()) && (stripos($url, 'https:') === 0)) {
+            $url = 'http:' . substr($url, 6);
+        }
+    }
+    
+    if (SIMPLEID_CLEAN_URL) {
+        $url .= $q . (($params == '') ? '' : '?' . $params);
+    } elseif (($q == '') && ($params == '')) {
+        $url .= '';
+    } elseif ($q == '') {
+        $url .= 'index.php?' . $params;
+    } else {
+        $url .= 'index.php?q=' . $q . (($params == '') ? '' : '&' . $params);
+    }
+    return $url;
+}
+
+/**
+ * Obtains the URL of the host of the SimpleID's installation.  The host is worked
+ * out based on SIMPLEID_BASE_URL
+ *
+ * @param string $secure if $relative is false, either 'https' to force an HTTPS connection, 'http' to force
+ * an unencrypted HTTP connection, or NULL to vary based on SIMPLEID_BASE_URL
+ * @return string the url
+ */
+function simpleid_host_url($secure = null) {
+    $parts = parse_url(SIMPLEID_BASE_URL);
+    
+    if ($secure == 'https') {
+        $scheme = 'https';
+    } elseif ($secure == 'http') {
+        $scheme = 'http';
+    } else {
+        $scheme = $parts['scheme'];
+    }
+    
+    $url = $scheme . '://';
+    if (isset($parts['user'])) {
+        $url .= $parts['user'];
+        if (isset($parts['pass'])) $url .= ':' . $parts['pass'];
+        $url .= '@';
+    }
+    $url .= $parts['host'];
+    if (isset($parts['port'])) $url .= ':' . $parts['port'];
+
+    return $url;
+}
+
+/**
+ * Returns a relatively unique cookie name based on a specified suffix and
+ * SIMPLEID_BASE_URL.
+ *
+ * @param string $suffix the cookie name suffix
+ * @return string the cookie name
+ */
+function simpleid_cookie_name($suffix) {
+    static $prefix = NULL;
+
+    if ($prefix == NULL) {
+        $prefix = substr(get_form_token('cookie', FALSE), 0, 7) . '_';
+    }
+    return $prefix . $suffix;
+}
+
+/**
+ * Obtains a form token given a form ID.
+ *
+ * Form tokens are used in SimpleID forms to guard against cross-site forgery
+ * attacks.
+ *
+ * @param string $id the form ID
+ * @param bool $bind_session whether to bind the form token to the current session
+ * @return string a form token
+ */
+function get_form_token($id, $bind_session = TRUE) {
+    global $user;
+
+    if (store_get('site-token') == NULL) {
+        $site_token = pack('LLLL', mt_rand(), mt_rand(), mt_rand(), mt_rand());
+        store_set('site-token', $site_token);
+    } else {
+        $site_token = store_get('site-token');
+    }
+    
+    return _get_form_token($site_token, $id, $bind_session);
+}
+
+/**
+ * Checks whether a form token is valid
+ *
+ * @param string $token the token returned by the user agent
+ * @param string $id the form ID
+ * @param bool $bind_session whether the token has been bound to the current session
+ * @return bool true if the form token is valid
+ */
+function validate_form_token($token, $id, $bind_session = TRUE) {
+    global $user;
+    
+    $site_token = store_get('site-token');
+    
+    return ($token == _get_form_token($site_token, $id, $bind_session));
+}
+
+function _get_form_token($site_token, $id, $bind_session = TRUE) {
+    global $user;
+
+    if (($user == NULL) || (!$bind_session)) {
+        $key = $site_token;
+    } else {
+        $key = session_id() . $site_token;
+    }
+
+    if (function_exists('hash_hmac') && function_exists('hash_algos') && (in_array('sha1', hash_algos()))) {
+        return hash_hmac('sha1', $id, $key);
+    } else {
+        if (strlen($site_token) > 64) {
+            $site_token = sha1($site_token, TRUE);
+        }
+    
+        $site_token = str_pad($site_token, 64, chr(0x00));
+        $ipad = str_repeat(chr(0x36), 64);
+        $opad = str_repeat(chr(0x5c), 64);
+        return bin2hex(sha1(($key ^ $opad) . sha1(($key ^ $ipad) . $text, TRUE), TRUE));
+    }
+}
+
+/* ------- SimpleID extension support ---------------------------------------- */
+
+
+/**
+ * This variable holds an array of extensions specified by the user
+ *
+ * @global array $simpleid_extensions
+ * @see SIMPLEID_EXTENSIONS
+ */
+$simpleid_extensions = array();
+
+/**
+ * Initialises the extension mechanism.  This function looks up the extensions
+ * to load in the {@link SIMPLEID_EXTENSIONS} constants, loads them, then
+ * calls the ns hook.
+ */
+function extension_init() {
+    global $simpleid_extensions;
+    
+    $simpleid_extensions = preg_split('/,\s*/', SIMPLEID_EXTENSIONS);
+    
+    foreach ($simpleid_extensions as $extension) {
+        include_once 'extensions/' . $extension . '/' . $extension . '.extension.php';
+    }
+}
+
+/**
+ * Invokes a hook in all the loaded extensions.
+ *
+ * @param string $function the name of the hook to call
+ * @param mixed $args the arguments to the hook
+ * @return array the return values from the hook
+ */
+function extension_invoke_all() {
+    global $simpleid_extensions;
+    
+    $args = func_get_args();
+    $function = array_shift($args);
+    $return = array();
+    
+    foreach ($simpleid_extensions as $extension) {
+        if (function_exists($extension . '_' . $function)) {
+            log_debug('extension_invoke_all: ' . $extension . '_' . $function);
+            $result = call_user_func_array($extension . '_' . $function, $args);
+            if (isset($result) && is_array($result)) {
+                $return = array_merge($return, $result);
+            } elseif (isset($result)) {
+                $return[] = $result;
+            } 
+        }
+    }
+    
+    return $return;
+}
+
+/**
+ * Invokes a hook in a specified extension.
+ *
+ * @param string $extension the extension to call
+ * @param string $function the name of the hook to call
+ * @param mixed $args the arguments to the hook
+ * @return mixed the return value from the hook
+ */
+function extension_invoke() {
+    $args = func_get_args();
+    $extension = array_shift($args);
+    $function = array_shift($args);
+    
+    if (function_exists($extension . '_' . $function)) {
+        log_debug('extension_invoke: ' . $extension . '_' . $function);
+        return call_user_func_array($extension . '_' . $function, $args);
+    }
+}
+
+/**
+ * Returns an array of currently loaded extensions.
+ *
+ * @return array a list of the names of the currently loaded extensions.
+ */
+function get_extensions() {
+    global $simpleid_extensions;
+    
+    return $simpleid_extensions;
+}
+?>