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