Add simpleid-1.0.5
diff --git a/simpleid/www/.htaccess.dist b/simpleid/www/.htaccess.dist
new file mode 100644
index 0000000..b962c35
--- /dev/null
+++ b/simpleid/www/.htaccess.dist
@@ -0,0 +1,51 @@
+# :mode=htaccess:
+#
+# SimpleID
+#
+# Copyright (C) Kelvin Mo 2009
+#
+# 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$
+
+# Protect files and directories from prying eyes.
+<FilesMatch "(\.(inc|cache|client|identity|xtpl|inc\.php|store\.php|extension\.php)|config\.php|locale)$">
+ Order deny,allow
+ Deny from all
+</FilesMatch>
+
+# Don't show directory listings for URLs which map to a directory.
+Options -Indexes
+
+# Various rewrite rules.
+<IfModule mod_rewrite.c>
+ RewriteEngine on
+
+ # Modify the RewriteBase if you are using SimpleID in a subdirectory and
+ # the rewrite rules are not working properly.
+ #RewriteBase /simpleid
+
+ # Pass Authorization header to PHP
+ RewriteCond %{HTTP:Authorization} ^(.*)
+ RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
+
+ # Rewrite URLs
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+</IfModule>
+
+# Switch register_globals to off
+php_value register_globals 0
diff --git a/simpleid/www/bignum.inc.php b/simpleid/www/bignum.inc.php
new file mode 100644
index 0000000..78fefd7
--- /dev/null
+++ b/simpleid/www/bignum.inc.php
@@ -0,0 +1,286 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Abstraction library for multiple precision mathematics. This file uses either
+ * the GNU Multiple Precision Arithmic Libary (GMP) if it is installed, or the
+ * default BCMath library if it is not installed.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+
+if (function_exists('gmp_init')) {
+ /**
+ * Defines whether the GMP library is available.
+ */
+ define('BIGNUM_GMP', true);
+} else {
+ /** @ignore */
+ define('BIGNUM_GMP', false);
+}
+
+/**
+ * Returns whether either the GMP or the BCMath library is installed. If neither
+ * of these libraries are installed, the functions in this file will not work.
+ *
+ * @return bool true if either GMP or BCMath is installed.
+ */
+function bignum_loaded() {
+ return (function_exists('gmp_init') || function_exists('bcadd'));
+}
+
+/**
+ * Creates a bignum.
+ *
+ * @param mixed $str An integer, a string in base 2 to 36, or a byte stream in base 256
+ * @param int $base an integer between 2 and 36, or 256
+ * @return resource a bignum
+ */
+function bignum_new($str, $base = 10) {
+ switch ($base) {
+ case 10:
+ if (BIGNUM_GMP) {
+ return gmp_init($str, 10);
+ } else {
+ return $str;
+ }
+ break;
+ case 256:
+ $bytes = array_merge(unpack('C*', $str));
+
+ $num = bignum_new(0);
+
+ foreach ($bytes as $byte) {
+ $num = bignum_mul($num, 256);
+ $num = bignum_add($num, bignum_new($byte));
+ }
+ return $num;
+ break;
+ default:
+ if (!is_integer($base) || ($base < 2) || ($base > 36)) return FALSE;
+
+ $num = bignum_new(0);
+
+ for ($i = 0; $i < strlen($str); $i++) {
+ $num = bignum_mul($num, $base);
+ $num = bignum_add($num, bignum_new(base_convert($str[$i], $base, 10)));
+ }
+ return $num;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Converts a bignum into a string representation (base 2 to 36) or a byte stream
+ * (base 256)
+ *
+ * @param resource $num the bignum
+ * @param int $base an integer between 2 and 36, or 256
+ * @return string the converted bignum
+ */
+function bignum_val($num, $base = 10) {
+ switch ($base) {
+ case 10:
+ if (BIGNUM_GMP) {
+ $base10 = gmp_strval($num, 10);
+ } else {
+ $base10 = $num;
+ }
+
+ return $base10;
+ break;
+
+ case 256:
+ $cmp = bignum_cmp($num, 0);
+ if ($cmp < 0) {
+ return FALSE;
+ }
+
+ if ($cmp == 0) {
+ return "\x00";
+ }
+
+ $bytes = array();
+
+ while (bignum_cmp($num, 0) > 0) {
+ array_unshift($bytes, bignum_mod($num, 256));
+ $num = bignum_div($num, 256);
+ }
+
+ if ($bytes && ($bytes[0] > 127)) {
+ array_unshift($bytes, 0);
+ }
+
+ $byte_stream = '';
+ foreach ($bytes as $byte) {
+ $byte_stream .= pack('C', $byte);
+ }
+
+ return $byte_stream;
+ break;
+ default:
+ if (!is_integer($base) || ($base < 2) || ($base > 36)) return FALSE;
+
+ $cmp = bignum_cmp($num, 0);
+ if ($cmp < 0) {
+ return FALSE;
+ }
+
+ if ($cmp == 0) {
+ return "0";
+ }
+
+ $str = '';
+ while (bignum_cmp($num, 0) > 0) {
+ $r = intval(bignum_val(bignum_mod($num, $base)));
+ $str = base_convert($r, 10, $base) . $str;
+ $num = bignum_div($num, $base);
+ }
+
+ return $str;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Adds two bignums
+ *
+ * @param resource $a
+ * @param resource $b
+ * @return resource a bignum representing a + b
+ */
+function bignum_add($a, $b) {
+ if (BIGNUM_GMP) {
+ return gmp_add($a, $b);
+ } else {
+ return bcadd($a, $b);
+ }
+}
+
+/**
+ * Multiplies two bignums
+ *
+ * @param resource $a
+ * @param resource $b
+ * @return resource a bignum representing a * b
+ */
+function bignum_mul($a, $b) {
+ if (BIGNUM_GMP) {
+ return gmp_mul($a, $b);
+ } else {
+ return bcmul($a, $b);
+ }
+}
+
+/**
+ * Divides two bignums
+ *
+ * @param resource $a
+ * @param resource $b
+ * @return resource a bignum representing a / b
+ */
+function bignum_div($a, $b) {
+ if (BIGNUM_GMP) {
+ return gmp_div($a, $b);
+ } else {
+ return bcdiv($a, $b);
+ }
+}
+
+/**
+ * Raise base to power exp
+ *
+ * @param resource $base the base
+ * @param mixed $exp the exponent, as an integer or a bignum
+ * @return resource a bignum representing base ^ exp
+ */
+function bignum_pow($base, $exp) {
+ if (BIGNUM_GMP) {
+ if (is_resource($exp) && (get_resource_type($exp) == 'gmp')) $exp = gmp_intval($exp);
+ return gmp_pow($base, $exp);
+ } else {
+ return bcpow($base, $exp);
+ }
+}
+
+/**
+ * Returns n modulo d
+ *
+ * @param resource $n
+ * @param resource $d
+ * @return resource a bignum representing n mod d
+ */
+function bignum_mod($n, $d) {
+ if (BIGNUM_GMP) {
+ return gmp_mod($n, $d);
+ } else {
+ return bcmod($n, $d);
+ }
+}
+
+/**
+ * Raise a number into power with modulo
+ *
+ * @param resource $base the base
+ * @param resource $exp the exponent
+ * @param resource $mod the modulo
+ * @return resource a bignum representing base ^ exp mod mod
+ */
+function bignum_powmod($base, $exp, $mod) {
+ if (BIGNUM_GMP) {
+ return gmp_powm($base, $exp, $mod);
+ } elseif (function_exists('bcpowmod')) {
+ return bcpowmod($base, $exp, $mod);
+ } else {
+ $square = bignum_mod($base, $mod);
+ $result = 1;
+ while (bignum_cmp($exp, 0) > 0) {
+ if (bignum_mod($exp, 2)) {
+ $result = bignum_mod(bignum_mul($result, $square), $mod);
+ }
+ $square = bignum_mod(bignum_mul($square, $square), $mod);
+ $exp = bignum_div($exp, 2);
+ }
+ return $result;
+ }
+}
+
+/**
+ * Compares two bignum
+ *
+ * @param resource $a
+ * @param resource $b
+ * @return int positive value if a > b, zero if a = b and a negative value if a < b
+ */
+function bignum_cmp($a, $b) {
+ if (BIGNUM_GMP) {
+ return gmp_cmp($a, $b);
+ } else {
+ return bccomp($a, $b);
+ }
+}
+?>
diff --git a/simpleid/www/cache.inc.php b/simpleid/www/cache.inc.php
new file mode 100644
index 0000000..d555e73
--- /dev/null
+++ b/simpleid/www/cache.inc.php
@@ -0,0 +1,232 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-9
+ *
+ * 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$
+ */
+
+/**
+ * Functions for caching and persistence.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+/**
+ * Stores data into the cache.
+ *
+ * @param string $type the type of data in the cache
+ * @param string $key an identifier
+ * @param mixed $data the data to store
+ * @param int $time if present, sets the modification time of the cache file to this
+ * time
+ */
+function cache_set($type, $key, $data, $time = NULL) {
+ $filename = _cache_get_name($type, $key);
+ if (!file_exists(dirname($filename))) mkdir(dirname($filename), 0775, true);
+ $file = fopen($filename, 'w');
+ fwrite($file, serialize($data));
+ fclose($file);
+
+ if ($time != NULL) {
+ touch($filename, $time);
+ }
+}
+
+/**
+ * Obtains data from the cache.
+ *
+ * @param string $type the type of data in the cache
+ * @param string $key an identifier
+ * @return mixed the data associated with the type and key, or NULL if the cache
+ * does not contain the requested data.
+ */
+function cache_get($type, $key) {
+ $filename = _cache_get_name($type, $key);
+
+ if (!file_exists($filename)) return NULL;
+
+ return unserialize(file_get_contents($filename));
+}
+
+/**
+ * Obtains all data of a particular type from the cache.
+ *
+ * @param string $type the type of data in the cache
+ * @return mixed an array of data associated with the type, or NULL if the cache
+ * does not contain the requested data.
+ */
+function cache_get_all($type) {
+ $r = array();
+
+ if (!is_dir(CACHE_DIR . '/' . $type)) return $r;
+ $dir = opendir(CACHE_DIR . '/' . $type);
+
+ while (($file = readdir($dir)) !== false) {
+ $filename = CACHE_DIR . '/' . $type . '/' . $file;
+
+ if (filetype($filename) != "file") continue;
+
+ $r[] = unserialize(file_get_contents($filename));
+ }
+
+ closedir($dir);
+
+ return $r;
+}
+
+/**
+ * Deletes data from the cache.
+ *
+ * @param string $type the type of data in the cache
+ * @param string $key an identifier
+ */
+function cache_delete($type, $key) {
+ $filename = _cache_get_name($type, $key);
+ if (file_exists($filename)) unlink($filename);
+}
+
+/**
+ * Garbage collects data stored the cache. Data is deleted if it was stored
+ * for longer than the specified expiry.
+ *
+ * This function is deprecated, use {@link cache_expire()}.
+ *
+ * @param int $expiry the expiry time, in seconds, after which data will be deleted
+ * @param string $type the type of data in the cache
+ * @deprecated
+ */
+function cache_gc($expiry, $type = NULL) {
+ if ($type == NULL) {
+ $dir = opendir(CACHE_DIR);
+
+ while (($file = readdir($dir)) !== false) {
+ $filename = CACHE_DIR . '/' . $file;
+ if (in_array(filetype($filename), array('dir', 'link')))
+ cache_gc($expiry, $file);
+ }
+ } else {
+ if (!is_dir(CACHE_DIR . '/' . $type)) return;
+ $dir = opendir(CACHE_DIR . '/' . $type);
+ while (($file = readdir($dir)) !== false) {
+ $filename = CACHE_DIR . '/' . $type . '/' . $file;
+
+ if ((filetype($filename) == "file") && (filectime($filename) < time() - $expiry))
+ unlink($filename);
+ }
+ }
+
+ closedir($dir);
+}
+
+/**
+ * Garbage collects data stored the cache. Data is deleted if it was stored
+ * for longer than the specified expiry.
+ *
+ * The parameter to this function takes either an integer or an array. If the
+ * parameter is an integer, everything in the cache older than the specified
+ * time (in seconds) will be deleted. If the parameter is an array,
+ * cache items of the type specified in the key to the array, older than the
+ * corresponding value will be deleted.
+ *
+ * This function is deprecated, use {@link cache_expire()}.
+ *
+ * @param int|array $params the expiry time, in seconds, after which data will be deleted,
+ * or an array specifiying the expiry time for each type
+ */
+function cache_expire($params) {
+
+ $dirs = array();
+ array_push($dirs, CACHE_DIR);
+ while (sizeof($dirs)) {
+ $dirname = array_pop($dirs);
+ if (($dir = opendir($dirname)) === false) {
+ continue;
+ }
+
+ while (($file = readdir($dir)) !== false) {
+ $expiry = NULL;
+ if ($file == '.' || $file == '..') {
+ continue;
+ }
+ $filename = $dirname . '/' . $file;
+
+ if (is_dir($filename)) {
+ array_push($dirs, $filename);
+ continue;
+ }
+
+ if (is_int($params)) {
+ $expiry = $params;
+ } elseif (is_array($params)) {
+ foreach ($params as $type => $param) {
+ if (strpos($filename, $type) !== false) {
+ $expiry = $param;
+ break;
+ }
+ }
+ }
+
+ if (!is_null($expiry) && (filetype($filename) == "file") && (filectime($filename) < time() - $expiry)) {
+ unlink($filename);
+ }
+ }
+
+ closedir($dir);
+ }
+}
+
+/**
+ * Returns the time remaining, in seconds, before the data associated with the
+ * type and key become subject to garbage collection by {@link cache_gc()}.
+ *
+ * @param string $type the type of data in the cache
+ * @param string $key an identifier
+ * @param int $expiry the expiry time, in seconds, which would be passed onto the
+ * {@link cache_gc()} function
+ * @return int the time remaining before expiry, rounded downwards,
+ * or zero if the cache does not contain the requested data
+ * @since 0.8
+ */
+function cache_ttl($type, $key, $expiry) {
+ $filename = _cache_get_name($type, $key);
+
+ if (!file_exists($filename)) return 0;
+
+ return filectime($filename) - (time() - $expiry) - 1;
+}
+
+/**
+ * Returns the name of the cache data file, given a type and an identifier.
+ *
+ * @param string $type the type of data in the cache
+ * @param string $key an identifier
+ * @return string a file name
+ */
+function _cache_get_name($type, $key) {
+ return CACHE_DIR . '/' . $type . '/' . md5($key) . '.cache';
+}
+
+?>
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;
+}
+?>
diff --git a/simpleid/www/config.default.php b/simpleid/www/config.default.php
new file mode 100644
index 0000000..a64e832
--- /dev/null
+++ b/simpleid/www/config.default.php
@@ -0,0 +1,61 @@
+<?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$
+ */
+/**
+ * Default configuration settings
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+/**
+ * Define a constant if it has not been defined already.
+ *
+ * @param $name string the name of the constant
+ * @param $value mixed the value of the constant - only scalar and null values
+ * are allowed
+ */
+function define_default($name, $value) {
+ if (!defined($name)) {
+ define($name, $value);
+ }
+}
+
+define_default('SIMPLEID_CLEAN_URL', false);
+define_default('SIMPLEID_ALLOW_PLAINTEXT', false);
+define_default('SIMPLEID_ALLOW_AUTOCOMPLETE', false);
+define_default('SIMPLEID_EXTENSIONS', 'sreg');
+define_default('SIMPLEID_VERIFY_RETURN_URL_USING_REALM', true);
+define_default('SIMPLEID_STORE', 'filesystem');
+define_default('SIMPLEID_STORE_DIR', SIMPLEID_CACHE_DIR);
+define_default('SIMPLEID_LOCALE', 'en');
+define_default('SIMPLEID_LOGFILE', '');
+define_default('SIMPLEID_LOGLEVEL', 4);
+
+if (function_exists('date_default_timezone_set')) date_default_timezone_set(@date_default_timezone_get());
+?>
diff --git a/simpleid/www/config.pear.php b/simpleid/www/config.pear.php
new file mode 100644
index 0000000..0d797a8
--- /dev/null
+++ b/simpleid/www/config.pear.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2012
+ *
+ * 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$
+ */
+
+/**
+ * Proxy for {@link config.php} for PEAR installations.
+ *
+ * @package pear
+ */
+
+include_once 'PEAR/Config.php';
+
+$pear_cfg_dir = PEAR_Config::singleton()->get('cfg_dir');
+include_once $pear_cfg_dir . '/simpleid/config.php';
+?>
diff --git a/simpleid/www/config.php.dist b/simpleid/www/config.php.dist
new file mode 100644
index 0000000..4601ae8
--- /dev/null
+++ b/simpleid/www/config.php.dist
@@ -0,0 +1,220 @@
+<?php
+/**
+ * SimpleID configuration file.
+ *
+ * @package simpleid
+ *
+ */
+/*
+ * $Id$
+ *
+ */
+
+
+/**
+ * Base URL.
+ *
+ * This is the URL of the location you want to place your SimpleID
+ * distribution. It becomes the URL of the SimpleID server.
+ *
+ * It is not allowed to have a trailing slash; SimpleID will add it
+ * for you.
+ *
+ * Examples:
+ * <code>
+ * define('SIMPLEID_BASE_URL', 'http://www.example.com');
+ * define('SIMPLEID_BASE_URL', 'http://www.example.com:8888');
+ * define('SIMPLEID_BASE_URL', 'http://www.example.com/simpleid');
+ * define('SIMPLEID_BASE_URL', 'https://www.example.com:8888/simpleid');
+ * </code>
+ *
+ */
+define('SIMPLEID_BASE_URL', 'http://www.example.com');
+
+/**
+ * Allow clean URLs.
+ *
+ * URLs used in SimpleID are normally in the form
+ * http://www.example.com/index.php?q=foo. Enabling clean URLs will allow for
+ * SimpleID URLs to be in the form http://www.example.com/foo
+ *
+ * In order to support clean URLs, you must be using Apache with mod_rewrite
+ * enabled. You will need to rename .htaccess.dist in the SimpleID web directory
+ * to .htaccess
+ *
+ * @since 0.8
+ *
+ */
+define('SIMPLEID_CLEAN_URL', false);
+
+/**
+ * Directory to store identity information.
+ *
+ * This directory must exist and be readable by the web server.
+ *
+ * For maximum security, it is highly recommended to place this
+ * directory outside your web browser's document root directory, so
+ * that it is not visible to user agents.
+ *
+ */
+define('SIMPLEID_IDENTITIES_DIR', '../identities');
+
+/**
+ * Directory to store cache data.
+ *
+ * This directory must exist and be readable and writable by the
+ * web server.
+ *
+ * For maximum security, it is highly recommended to place this
+ * directory outside your web browser's document root directory, so
+ * that it is not visible to user agents.
+ *
+ */
+define('SIMPLEID_CACHE_DIR', '../cache');
+
+/**
+ * Persistent data storage mechanism.
+ *
+ * SimpleID provides flexible methods to store and retrieve persistent data.
+ * By default, SimpleID uses the file system to store this data, implemented
+ * in filesystem.store.inc. Users can implement other methods by creating
+ * a file with extension .store.inc and specifying the file through this
+ * setting.
+ *
+ * Generally you do not need to change this setting.
+ *
+ */
+define('SIMPLEID_STORE', 'filesystem');
+
+/**
+ * Directory to store persistent data.
+ *
+ * This directory must exist and be readable and writable by the
+ * web server.
+ *
+ * For maximum security, it is highly recommended to place this
+ * directory outside your web browser's document root directory, so
+ * that it is not visible to user agents.
+ *
+ */
+define('SIMPLEID_STORE_DIR', '../store');
+
+/**
+ * Allows use of unencrypted connections.
+ *
+ * Between versions 0.6 and 0.8 (inclusive), SimpleID uses either HTTPS or
+ * a form of digest authentication for its login system. This allows passwords
+ * and other secure information not to be sent to the server as plaintext.
+ *
+ * From version 0.9, SimpleID mandates the use of HTTPS for all connections
+ * (other than direct connections between SimpleID and an OpenID relying
+ * party). However, for debug purposes, it may be necessary to allow
+ * unencrypted connections to SimpleID.
+ *
+ * It is strongly recommended that this is set to false. Setting this to true
+ * will allow passwords to be sent as plaintext. You should not change this
+ * value unless it is absolutely necessary.
+ *
+ * @since 0.9
+ */
+define('SIMPLEID_ALLOW_PLAINTEXT', false);
+
+/**
+ * Allows web browsers to save passwords.
+ *
+ * SimpleID prevents web browsers from saving user passwords entered in a user
+ * logs into SimpleID. Setting this value to true will allow browsers to
+ * ask the user whether the password should be saved in the browser's password
+ * store.
+ *
+ * The default is set to false for security reasons. You should not change
+ * this value unless you are certain regarding the security of your browser's
+ * password store.
+ *
+ * @since 0.8
+ */
+define('SIMPLEID_ALLOW_AUTOCOMPLETE', false);
+
+/**
+ * Performs additional verification of relying party return URLs.
+ *
+ * When authenticating using OpenID version 2, SimpleID version 0.7 or later
+ * can perform additional verification of the relying party's return URLs under
+ * section 9.2.1 of the OpenID specification.
+ *
+ * The default is set to true for security reasons. However, if your web server
+ * is blocked by your web hosting provider's firewall from accessing outside
+ * servers, then set this to false.
+ *
+ * @since 0.7
+ *
+ */
+define('SIMPLEID_VERIFY_RETURN_URL_USING_REALM', true);
+
+
+/**
+ * The locale for the SimpleID user interface.
+ *
+ * @since 0.9
+ */
+define('SIMPLEID_LOCALE', 'en');
+
+/**
+ * Date and time format.
+ *
+ * The date and time format specified using the strftime() syntax.
+ *
+ * See http://www.php.net/strftime for details.
+ *
+ */
+define('SIMPLEID_DATE_TIME_FORMAT', '%Y-%m-%d %H:%M:%S %Z');
+
+/**
+ * The number of seconds before associations expire. This is an advanced
+ * option, for which the default setting should work fine.
+ *
+ * Note that for ICAM compliance, this number must be less than 86400.
+ */
+define('SIMPLEID_ASSOC_EXPIRES_IN', 3600);
+
+/**
+ * SimpleID extensions.
+ *
+ * The SimpleID extensions you wish to load. You should separate the
+ * extensions you wish to load with a comma.
+ *
+ */
+define('SIMPLEID_EXTENSIONS', 'sreg,ui');
+
+/**
+ * Log file.
+ *
+ * You can specify a file into which SimpleID will log various diagnostic
+ * messages.
+ *
+ * The log file's directory must exist and must be writable by the web server.
+ *
+ * To disable logging, set this as an empty string.
+ *
+ * @since 0.7
+ *
+ */
+define('SIMPLEID_LOGFILE', '');
+
+/**
+ * The level of detail for log messages.
+ *
+ * You can determine which messages are captured in the log file by specifying
+ * a number between 0 and 5. The higher the number, the more messages are
+ * logged.
+ *
+ * WARNING: Setting the log level to 5 will result in security sensitive
+ * information also being logged.
+ *
+ * This has effect only if logging is enabled.
+ *
+ * @since 0.7
+ *
+ */
+define('SIMPLEID_LOGLEVEL', 4);
+?>
diff --git a/simpleid/www/discovery.inc.php b/simpleid/www/discovery.inc.php
new file mode 100644
index 0000000..30c50e6
--- /dev/null
+++ b/simpleid/www/discovery.inc.php
@@ -0,0 +1,649 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-9
+ *
+ * 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$
+ */
+
+/**
+ * Support for XRDS based discovery.
+ *
+ * The functions for this file supports HTTP-based identifiers. For XRIs, the
+ * resolution service xri.net is used to resolve to HTTP-based URLs.
+ *
+ * @package simpleid
+ * @since 0.7
+ * @filesource
+ */
+
+include_once "http.inc.php";
+
+/**
+ * The namespace identifier for an XRDS document.
+ */
+define('XRDS_NS', 'xri://$xrds');
+
+/**
+ * The namespace identifier for XRDS version 2.
+ */
+define('XRD2_NS', 'xri://$xrd*($v*2.0)');
+
+/**
+ * The namespace identifier for XRDS Simple.
+ */
+define('XRDS_SIMPLE_NS', 'http://xrds-simple.net/core/1.0');
+
+/**
+ * The type identifier for XRDS Simple.
+ */
+define('XRDS_SIMPLE_TYPE', 'xri://$xrds*simple');
+
+/**
+ * The namespace identifier for OpenID services.
+ */
+define('XRD_OPENID_NS', 'http://openid.net/xmlns/1.0');
+
+/**
+ * Obtains the services for particular identifier.
+ *
+ * This function attempts to discover and obtain the XRDS document associated
+ * with the identifier, parses the XRDS document and returns an array of
+ * services.
+ *
+ * If an XRDS document is not found, and $openid is set to true, this function
+ * will also attempt to discover OpenID services by looking for link elements
+ * with rel of openid.server or openid2.provider in the discovered HTML document.
+ *
+ * @param string $identifier the identifier
+ * @param bool $openid if true, performs additional discovery of OpenID services
+ * by looking for link elements within the discovered document
+ * @return array an array of discovered services, or an empty array if no services
+ * are found
+ */
+function discovery_xrds_discover($identifier, $openid = FALSE) {
+ $identifier = discovery_xrds_normalize($identifier);
+ $url = discovery_xrds_url($identifier);
+
+ $xrds = discovery_xrds_get($url);
+
+ if ($xrds) {
+ return discovery_xrds_parse($xrds);
+ } else {
+ if ($openid) return discovery_html_get_services($url);
+ return array();
+ }
+}
+
+/**
+ * Given an array of discovered services, obtains information on services of
+ * a particular type.
+ *
+ * @param array $services the discovered services
+ * @param string $type the URI of the type of service to obtain
+ * @return array an array of matching services, or an empty array of no services
+ * match
+ */
+function discovery_xrds_services_by_type($services, $type) {
+ $matches = array();
+
+ foreach ($services as $service) {
+ foreach ($service['type'] as $service_type) {
+ if ($service_type == $type) $matches[] = $service;
+ }
+ }
+ return $matches;
+}
+
+/**
+ * Given an array of discovered services, obtains information on the service of
+ * a specified ID.
+ *
+ * @param array $services the discovered services
+ * @param string $id the XML ID of the service in the XRDS document
+ * @return array the matching service, or NULL of no services
+ * are found
+ */
+function discovery_xrds_service_by_id($services, $id) {
+ foreach ($services as $service) {
+ if ($service['#id'] == $id) return $service;
+ }
+ return NULL;
+}
+
+/**
+ * Obtains a XRDS document at a particular URL. Performs Yadis discovery if
+ * the URL does not produce a XRDS document.
+ *
+ * @param string $url the URL
+ * @param bool $check whether to check the content type of the response is
+ * application/xrds+xml
+ * @param int $retries the number of tries to make
+ * @return string the contents of the XRDS document
+ */
+function discovery_xrds_get($url, $check = TRUE, $retries = 5) {
+ if ($retries == 0) return NULL;
+
+ $response = http_make_request($url, array('Accept' => 'application/xrds+xml'));
+
+ if (isset($response['http-error'])) return NULL;
+ if (($response['content-type'] == 'application/xrds+xml') || ($check == FALSE)) {
+ return $response['data'];
+ } elseif (isset($response['headers']['x-xrds-location'])) {
+ return discovery_xrds_get($response['headers']['x-xrds-location'], false, $retries - 1);
+ } elseif (isset($response['data'])) {
+ $location = _discovery_meta_httpequiv('X-XRDS-Location', $response['data']);
+ if ($location) {
+ return discovery_xrds_get($location, false, $retries - 1);
+ }
+ return NULL;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Normalises an identifier for discovery.
+ *
+ * If the identifier begins with xri://, acct: or mailto:, this is stripped out. If the identifier
+ * does not begin with a valid URI scheme, http:// is assumed and added to the
+ * identifier.
+ *
+ * @param string $identifier the identifier to normalise
+ * @return string the normalised identifier
+ */
+function discovery_xrds_normalize($identifier) {
+ $normalized = $identifier;
+
+ if (discovery_is_xri($identifier)) {
+ if (stristr($identifier, 'xri://') !== false) $normalized = substr($identifier, 6);
+ } elseif (discovery_is_email($identifier)) {
+ if (stristr($identifier, 'acct:') !== false) $normalized = substr($identifier, 5);
+ if (stristr($identifier, 'mailto:') !== false) $normalized = substr($identifier, 7);
+ } else {
+ if (stristr($identifier, '://') === false) $normalized = 'http://'. $identifier;
+ if (substr_count($normalized, '/') < 3) $normalized .= '/';
+ }
+
+ return $normalized;
+}
+
+/**
+ * Obtains a URL for an identifier. If the identifier is a XRI, the XRI resolution
+ * service is used to convert the identifier to a URL.
+ *
+ * @param string $identifier the identifier
+ * @return string the URL
+ */
+function discovery_xrds_url($identifier) {
+ if (discovery_is_xri($identifier)) {
+ return 'http://xri.net/' . $identifier;
+ } elseif (discovery_is_email($identifier)) {
+ //list($user, $host) = explode('@', $identifier, 2);
+ //$host_meta = 'http://' . $host . '/.well-known/host-meta';
+ } else {
+ return $identifier;
+ }
+
+}
+
+/**
+ * Determines whether an identifier is an XRI.
+ *
+ * XRI identifiers either start with xri:// or with @, =, +, $ or !.
+ *
+ * @param string $identifier the parameter to test
+ * @return bool true if the identifier is an XRI
+ */
+function discovery_is_xri($identifier) {
+ $firstchar = substr($identifier, 0, 1);
+ if ($firstchar == "@" || $firstchar == "=" || $firstchar == "+" || $firstchar == "\$" || $firstchar == "!") return true;
+ if (stristr($identifier, 'xri://') !== FALSE) return true;
+ return false;
+}
+
+/**
+ * Determines whether an identifier is an e-mail address.
+ *
+ * An identifier is an e-mail address if it:
+ *
+ * - has a single @ character
+ * - does not have a slash character
+ *
+ * @param string $identifier the parameter to test
+ * @return bool true if the identifier is an e-mail address
+ */
+function discovery_is_email($identifier) {
+ // If it begins with acct: or mailto:, strip it out
+ if (stristr($identifier, 'acct:') !== false) $identifier = substr($identifier, 5);
+ if (stristr($identifier, 'mailto:') !== false) $identifier = substr($identifier, 7);
+
+ // If it contains a slash, it is not an e-mail address
+ if (strpos($identifier, "/") !== false) return false;
+
+ $at = strpos($identifier, "@");
+
+ // If it does not contain a @, it is not an e-mail address
+ if ($at === false) return false;
+
+ // If it contains more than one @, it is not an e-mail
+ if (strrpos($identifier, "@") != $at) return false;
+
+ return true;
+}
+
+
+/**
+ * Callback function to sort service and URI elements based on priorities
+ * specified in the XRDS document.
+ *
+ * The XRDS specification allows multiple instances of certain elements, such
+ * as Service and URI. The specification allows an attribute called priority
+ * so that the document creator can specify the order the elements should be used.
+ *
+ * @param array $a
+ * @param array $b
+ * @return int
+ */
+function discovery_xrds_priority_sort($a, $b) {
+ if (!isset($a['#priority']) && !isset($b['#priority'])) return 0;
+
+ // if #priority is missing, #priority is assumed to be infinity
+ if (!isset($a['#priority'])) return 1;
+ if (!isset($b['#priority'])) return -1;
+
+ if ($a['#priority'] == $b['#priority']) return 0;
+ return ($a['#priority'] < $b['#priority']) ? -1 : 1;
+}
+
+/**
+ * Parses an XRDS document to return services available.
+ *
+ * @param string $xrds the XRDS document
+ * @return array the parsed structure
+ *
+ * @see XRDSParser
+ */
+function discovery_xrds_parse($xrds) {
+ $parser = new XRDSParser();
+ $parser->parse($xrds);
+ $parser->free();
+ $services = $parser->services();
+ uasort($services, 'discovery_xrds_priority_sort');
+
+ return $services;
+}
+
+/**
+ * Obtains the OpenID services for particular identifier by scanning for link
+ * elements in the returned document.
+ *
+ * Note that this function does not use the YADIS protocol to scan for services.
+ * To use the YADIS protocol, use {@link discovery_get_services()}.
+ *
+ * @param string $url the URL
+ * @return array an array of discovered services, or an empty array if no services
+ * are found
+ */
+function discovery_html_get_services($url) {
+ $services = array();
+
+ $response = http_make_request($url);
+ $html = $response['data'];
+
+ $uri = _discovery_link_rel('openid2.provider', $html);
+ $delegate = _discovery_link_rel('openid2.local_id', $html);
+
+ if ($uri) {
+ $service = array(
+ 'type' => 'http://specs.openid.net/auth/2.0/signon',
+ 'uri' => $uri
+ );
+ if ($delegate) $service['localid'] = $delegate;
+ $services[] = $service;
+ }
+
+ $uri = _discovery_link_rel('openid.server', $html);
+ $delegate = _discovery_link_rel('openid.delegate', $html);
+
+ if ($uri) {
+ $service = array(
+ 'type' => 'http://openid.net/signon/1.0',
+ 'uri' => $uri
+ );
+ if ($delegate) $service['localid'] = $delegate;
+ $services[] = $service;
+ }
+
+ return $services;
+}
+
+/**
+ * Searches through an HTML document to obtain the value of a meta
+ * element with a specified http-equiv attribute.
+ *
+ * @param string $equiv the http-equiv attribute for which to search
+ * @param string $html the HTML document to search
+ * @return mixed the value of the meta element, or FALSE if the element is not
+ * found
+ */
+function _discovery_meta_httpequiv($equiv, $html) {
+ $html = preg_replace('/<!(?:--(?:[^-]*|-[^-]+)*--\s*)>/', '', $html); // Strip html comments
+
+ $equiv = preg_quote($equiv);
+ preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches);
+ if (isset($matches[1])) {
+ preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
+ if (isset($content[1])) {
+ return $content[1];
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Searches through an HTML document to obtain the value of a link
+ * element with a specified rel attribute.
+ *
+ * @param string $rel the rel attribute for which to search
+ * @param string $html the HTML document to search
+ * @return mixed the href of the link element, or FALSE if the element is not
+ * found
+ */
+function _discovery_link_rel($rel, $html) {
+ $html = preg_replace('/<!(?:--(?:[^-]*|-[^-]+)*--\s*)>/s', '', $html); // Strip html comments
+
+ $rel = preg_quote($rel);
+ preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches);
+ if (isset($matches[3])) {
+ preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
+ return trim($href[1]);
+ }
+ return FALSE;
+}
+
+/**
+ * A simple XRDS parser.
+ *
+ * This parser uses the classic expat functions available in PHP to parse the
+ * XRDS Simple XML document.
+ *
+ * The result is an array of discovered services.
+ *
+ * @link http://xrds-simple.net/
+ */
+class XRDSParser {
+ /**
+ * XML parser
+ * @var resource
+ * @access private
+ */
+ var $parser;
+
+ /**
+ * Discovered services
+ * @var array
+ * @access private
+ */
+ var $services = array();
+
+ /**
+ * State: are we parsing a service element?
+ * @var bool
+ * @access private
+ */
+ var $in_service = FALSE;
+
+ /**
+ * CDATA buffer
+ * @var string
+ * @access private
+ */
+ var $_buffer;
+ /**
+ * Attributes buffer
+ * @var array
+ * @access private
+ */
+ var $_attribs = array();
+
+ /**
+ * priority attribute buffer
+ * @var string
+ * @access private
+ */
+ var $priority = NULL;
+
+ /**
+ * Currently parsed service buffer
+ * @var array
+ * @access private
+ */
+ var $service = array();
+
+ /**
+ * Creates an instance of the XRDS parser.
+ *
+ * This constructor also initialises the underlying XML parser.
+ */
+ function XRDSParser() {
+ $this->parser = xml_parser_create_ns();
+ xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING,0);
+ xml_set_object($this->parser, $this);
+ xml_set_element_handler($this->parser, 'element_start', 'element_end');
+ xml_set_character_data_handler($this->parser, 'cdata');
+ }
+
+ /**
+ * Frees memory associated with the underlying XML parser.
+ *
+ * Note that only the memory associated with the underlying XML parser is
+ * freed. Memory associated with the class itself is not freed.
+ *
+ * @access public
+ */
+ function free() {
+ xml_parser_free($this->parser);
+ }
+
+ /**
+ * Parses an XRDS document.
+ *
+ * Once the parsing is complete, use {@link XRDSParser::services()} to obtain
+ * the services extracted from the document.
+ *
+ * @param string $xml the XML document to parse
+ * @access public
+ */
+ function parse($xml) {
+ xml_parse($this->parser, $xml);
+ }
+
+ /**
+ * Gets an array of discovered services.
+ *
+ * @return array an array of discovered services, or an empty array
+ * @access public
+ * @see XRDSParser::parse()
+ */
+ function services() {
+ return $this->services;
+ }
+
+ /**
+ * XML parser callback
+ *
+ * @access private
+ */
+ function element_start(&$parser, $qualified, $attribs) {
+ list($ns, $name) = $this->parse_namespace($qualified);
+
+ // Strictly speaking, XML namespace URIs are semi-case sensitive
+ // (i.e. the scheme and host are not case sensitive, but other elements
+ // are). However, the XRDS-Simple specifications defines a
+ // namespace URI for XRD (xri://$XRD*($v*2.0) rather than xri://$xrd*($v*2.0))
+ // with an unusual case.
+ if ((strtolower($ns) == strtolower(XRD2_NS)) && ($name == 'Service')) {
+ $this->in_service = TRUE;
+ $this->service = array();
+
+ if (in_array('priority', $attribs)) {
+ $this->service['#priority'] = $attribs['priority'];
+ }
+ if (in_array('id', $attribs)) {
+ $this->service['#id'] = $attribs['id'];
+ }
+ }
+
+ if ((strtolower($ns) == strtolower(XRD2_NS)) && ($this->in_service)) {
+ switch ($name) {
+ case 'Type':
+ case 'LocalID':
+ case 'URI':
+ if (in_array('priority', $attribs)) {
+ $this->priority = $attribs['priority'];
+ } else {
+ $this->priority = NULL;
+ }
+ }
+ }
+
+ $this->_buffer = '';
+ $this->_attribs = $attribs;
+ }
+
+ /**
+ * XML parser callback
+ *
+ * @access private
+ */
+ function element_end(&$parser, $qualified) {
+ list($ns, $name) = $this->parse_namespace($qualified);
+
+ if ((strtolower($ns) == strtolower(XRD2_NS)) && ($this->in_service)) {
+ switch ($name) {
+ case 'Service':
+ foreach (array('type', 'localid', 'uri') as $key) {
+ if (!isset($this->service[$key])) continue;
+ $this->service[$key] = $this->flatten_uris($this->service[$key]);
+ }
+
+ $this->services[] = $this->service;
+ $this->in_service = FALSE;
+ break;
+
+ case 'Type':
+ case 'LocalID':
+ case 'URI':
+ $key = strtolower($name);
+ if (!isset($this->service[$key])) {
+ $this->service[$key] = array();
+ }
+ if ($this->priority != NULL) {
+ $this->service[$key][] = array('#uri' => trim($this->_buffer), '#priority' => $this->priority);
+ } else {
+ $this->service[$key][] = array('#uri' => trim($this->_buffer));
+ }
+ $this->priority = NULL;
+ break;
+ }
+ }
+
+ if ((strtolower($ns) == strtolower(XRD_OPENID_NS)) && ($this->in_service)) {
+ switch ($name) {
+ case 'Delegate':
+ $this->service['delegate'] = trim($this->_buffer);
+ }
+ }
+
+ $this->_attribs = array();
+ }
+
+ /**
+ * XML parser callback
+ *
+ * @access private
+ */
+ function cdata(&$parser, $data) {
+ $this->_buffer .= $data;
+ }
+
+ /**
+ * Parses a namespace-qualified element name.
+ *
+ * @param string $qualified the qualified name
+ * @return array an array with two elements - the first element contains
+ * the namespace qualifier (or an empty string), the second element contains
+ * the element name
+ * @access protected
+ */
+ function parse_namespace($qualified) {
+ $pos = strrpos($qualified, ':');
+ if ($pos !== FALSE) return array(substr($qualified, 0, $pos), substr($qualified, $pos + 1, strlen($qualified)));
+ return array('', $qualified);
+ }
+
+ /**
+ * Flattens the service array.
+ *
+ * In an XRDS document, child elements of the service element often contains
+ * a list of URIs, with the priority specified in the priority attribute.
+ *
+ * When the document is parsed in this class, the URI and the priority are first
+ * extracted into the #uri and the #priority keys respectively. This function
+ * takes this array, sorts the elements using the #priority keys (if $sort is
+ * true), then collapses the array using the value associated with the #uri key.
+ *
+ * @param array $array the service array, with URIs and priorities
+ * @param bool $sort whether to sort the service array using the #priority
+ * keys
+ * @return array the services array with URIs sorted by priority
+ * @access protected
+ */
+ function flatten_uris($array, $sort = TRUE) {
+ $result = array();
+
+ if ($sort) uasort($array, 'discovery_xrds_priority_sort');
+
+ for ($i = 0; $i < count($array); $i++) {
+ $result[] = $array[$i]['#uri'];
+ }
+
+ return $result;
+ }
+}
+
+if (!function_exists('rfc3986_urlencode')) {
+ /**
+ * Encodes a URL using RFC 3986.
+ *
+ * PHP's rawurlencode function encodes a URL using RFC 1738. RFC 1738 has been
+ * updated by RFC 3986, which change the list of characters which needs to be
+ * encoded.
+ *
+ * Strictly correct encoding is required for various purposes, such as OAuth
+ * signature base strings.
+ *
+ * @param string $s the URL to encode
+ * @return string the encoded URL
+ */
+ function rfc3986_urlencode($s) {
+ return str_replace('%7E', '~', rawurlencode($s));
+ }
+}
+?>
diff --git a/simpleid/www/extensions/ax/ax.extension.php b/simpleid/www/extensions/ax/ax.extension.php
new file mode 100644
index 0000000..1286c52
--- /dev/null
+++ b/simpleid/www/extensions/ax/ax.extension.php
@@ -0,0 +1,327 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Implements the Attribute Exchange extension.
+ *
+ *
+ * @package simpleid
+ * @subpackage extensions
+ * @filesource
+ */
+
+/** Namespace for the AX extension */
+define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0');
+
+/** @ignore */
+global $ax_sreg_map;
+
+/**
+ * A mapping between Type URIs defined for Attribute Exchange and the corresponding
+ * property for the Simple Registration Extension
+ *
+ * @link http://www.axschema.org/types/#sreg
+ * @global array
+ */
+$ax_sreg_map = array(
+ 'http://axschema.org/namePerson/friendly' => 'nickname',
+ 'http://axschema.org/contact/email' => 'email',
+ 'http://axschema.org/namePerson' => 'fullname',
+ 'http://axschema.org/birthDate' => 'dob',
+ 'http://axschema.org/person/gender' => 'gender',
+ 'http://axschema.org/contact/postalCode/home' => 'postcode',
+ 'http://axschema.org/contact/country/home' => 'country',
+ 'http://axschema.org/pref/language' => 'language',
+ 'http://axschema.org/pref/timezone' => 'timezone',
+ 'http://openid.net/schema/namePerson/friendly' => 'nickname',
+ 'http://openid.net/schema/contact/internet/email' => 'email',
+ 'http://openid.net/schema/gender' => 'gender',
+ 'http://openid.net/schema/contact/postalCode/home' => 'postcode',
+ 'http://openid.net/schema/contact/country/home' => 'country',
+ 'http://openid.net/schema/language/pref' => 'language',
+ 'http://openid.net/schema/timezone' => 'timezone'
+);
+
+/**
+ * Returns the support for AX in SimpleID XRDS document
+ *
+ * @return array
+ * @see hook_xrds_types()
+ */
+function ax_xrds_types() {
+ return array(OPENID_NS_AX);
+}
+
+/**
+ * @see hook_response()
+ */
+function ax_response($assertion, $request) {
+ global $user;
+ global $version;
+ global $ax_sreg_map;
+
+ // We only deal with positive assertions
+ if (!$assertion) return array();
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_AX, $request)) return array();
+
+ $request = openid_extension_filter_request(OPENID_NS_AX, $request);
+ if (!isset($request['mode'])) return array();
+ $mode = $request['mode'];
+
+ $response = array();
+ $alias = openid_extension_alias(OPENID_NS_AX);
+ $response['openid.ns.' . $alias] = OPENID_NS_AX;
+
+ if ($mode == 'fetch_request') {
+ $response['openid.' . $alias . '.mode'] = 'fetch_response';
+
+ $required = (isset($request['required'])) ? explode(',', $request['required']) : array();
+ $optional = (isset($request['if_available'])) ? explode(',', $request['if_available']) : array();
+ $fields = array_merge($required, $optional);
+
+ foreach ($fields as $field) {
+ $type = $request['type.' . $field];
+ $response['openid.' . $alias . '.type.' . $field] = $type;
+ $value = _ax_get_value($type);
+
+ if ($value == NULL) {
+ $response['openid.' . $alias . '.count.' . $field] = 0;
+ } elseif (is_array($value)) {
+ $response['openid.' . $alias . '.count.' . $field] = count($value);
+ for ($i = 0; $i < count($value); $i++) {
+ $response['openid.' . $alias . '.value.' . $field . '.' . ($i + 1)] = $value[$i];
+ }
+ } else {
+ $response['openid.' . $alias . '.value.' . $field] = $value;
+ }
+ }
+ } elseif ($mode == 'store_request') {
+ // Sadly, we don't support storage at this stage
+ $response['openid.' . $alias . '.mode'] = 'store_response_failure';
+ $response['openid.' . $alias . '.error'] = 'OpenID provider does not support storage of attributes';
+ }
+
+ return $response;
+}
+
+/**
+ * Returns an array of fields that need signing.
+ *
+ * @see hook_signed_fields()
+ */
+function ax_signed_fields($response) {
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_AX, $response)) return array();
+
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_AX, $response));
+ $alias = openid_extension_alias(OPENID_NS_AX);
+ $signed_fields = array();
+
+ if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias;
+ foreach ($fields as $field) {
+ if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field;
+ }
+
+ return $signed_fields;
+}
+
+/**
+ * @see hook_consent_form()
+ */
+function ax_consent_form($request, $response, $rp) {
+ global $user;
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_AX, $request)) return '';
+
+ $request = openid_extension_filter_request(OPENID_NS_AX, $request);
+ if (!isset($request['mode'])) return '';
+ $mode = $request['mode'];
+
+ $xtpl2 = new XTemplate('extensions/ax/ax.xtpl');
+
+ if ($mode == 'fetch_request') {
+ $xtpl2->assign('alias', openid_extension_alias(OPENID_NS_AX));
+
+ $required = (isset($request['required'])) ? explode(',', $request['required']) : array();
+ $optional = (isset($request['if_available'])) ? explode(',', $request['if_available']) : array();
+ $fields = array_merge($required, $optional);
+ $i = 1;
+
+ foreach ($fields as $field) {
+ $type = $request['type.' . $field];
+ $value = _ax_get_value($type);
+
+ $xtpl2->assign('name', htmlspecialchars($type, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->assign('id', $i);
+
+ if (is_array($value)) {
+ $xtpl2->assign('value', htmlspecialchars(implode(',', $value), ENT_QUOTES, 'UTF-8'));
+ } elseif ($value != NULL) {
+ $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
+ }
+
+ $xtpl2->assign('checked', (in_array($field, $required) || !isset($rp['ax_consents']) || in_array($field, $rp['ax_consents'])) ? 'checked="checked"' : '');
+ $xtpl2->assign('disabled', (in_array($field, $required)) ? 'disabled="disabled"' : '');
+ if (in_array($field, $required)) $xtpl2->parse('fetch_request.ax.required');
+
+ $xtpl2->parse('fetch_request.ax');
+
+ $i++;
+ }
+
+ $xtpl2->assign('ax_data', t('SimpleID will also be sending the following information to the site.'));
+ $xtpl2->assign('name_label', t('Type URL'));
+ $xtpl2->assign('value_label', t('Value'));
+
+ $xtpl2->parse('fetch_request');
+ return $xtpl2->text('fetch_request');
+ } elseif ($mode == 'store_request') {
+ // Sadly, we don't support storage at this stage
+ $xtpl2->assign('store_request_message', t('This web site requested to store information about you on SimpleID. Sadly, SimpleID does not support this feature.'));
+ $xtpl2->parse('store_request');
+ return $xtpl2->text('store_request');
+ }
+}
+
+/**
+ * @see hook_consent()
+ */
+function ax_consent($form_request, &$response, &$rp) {
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_AX, $response)) return array();
+
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_AX, $response));
+ $alias = openid_extension_alias(OPENID_NS_AX);
+
+ foreach ($fields as $field) {
+ if ((strpos($field, 'value.') !== 0) && (strpos($field, 'count.') !== 0)) continue;
+
+ $type_alias = (strpos($field, '.', 6) === FALSE) ? substr($field, 6) : substr($field, strpos($field, '.', 6) - 6);
+ $type = $response['openid.' . $alias . '.type.' . $type_alias];
+
+ if (isset($response['openid.' . $alias . '.' . $field])) {
+ if (!in_array($type, $form_request['ax_consents'])) {
+ unset($response['openid.' . $alias . '.' . $field]);
+ }
+ }
+ }
+ foreach ($fields as $field) {
+ if (strpos($field, 'type.') !== 0) continue;
+ $type = $response['openid.' . $alias . '.' . $field];
+
+ if (isset($response['openid.' . $alias . '.' . $field])) {
+ if (!in_array($type, $form_request['ax_consents'])) {
+ unset($response['openid.' . $alias . '.' . $field]);
+ }
+ }
+ }
+
+ if (count(array_keys(openid_extension_filter_request(OPENID_NS_AX, $response))) == 0) {
+ // We have removed all the responses, so we remove the namespace as well
+ unset($response['openid.ns.' . $alias]);
+ }
+
+ $rp['ax_consents'] = $form_request['ax_consents'];
+}
+
+/**
+ * @see hook_page_profile()
+ */
+function ax_page_profile() {
+ global $user;
+ $xtpl2 = new XTemplate('extensions/ax/ax.xtpl');
+
+ if (isset($user['ax'])) {
+ foreach ($user['ax'] as $name => $value) {
+ $xtpl2->assign('name', htmlspecialchars($name, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->parse('user_page.ax');
+ }
+ }
+
+ $xtpl2->assign('ax_data', t('SimpleID will send the following information to sites which supports the Attribute Exchange Extension. If you have also supplied OpenID Connect user information in your identity, or have the Simple Registration Extension installed, these may also be sent as part of this Extension.'));
+ $xtpl2->assign('edit_identity_file', t('To change these, <a href="!url">edit your identity file</a>.', array('!url' => 'http://simpleid.org/docs/1/identity-files/')));
+ $xtpl2->assign('name_label', t('Type URL'));
+ $xtpl2->assign('value_label', t('Value'));
+
+ $xtpl2->parse('user_page');
+
+ return array(array(
+ 'id' => 'ax',
+ 'title' => t('Attribute Exchange Extension'),
+ 'content' => $xtpl2->text('user_page')
+ ));
+}
+
+/**
+ * Looks up the value of a specified Attribute Exchange Extension type URI.
+ *
+ * This function looks up the ax section of the user's identity file. If the
+ * specified type cannot be found, it looks up the corresponding field in the
+ * OpenID Connect user information (user_info section) and the Simple Registration
+ * Extension (sreg section).
+ *
+ * @param string $type the type URI to look up
+ * @return string the value or NULL if not found
+ */
+function _ax_get_value($type) {
+ global $user;
+ global $ax_sreg_map;
+
+ if (isset($user['ax'][$type])) {
+ return $user['ax'][$type];
+ } else {
+ // Look up OpenID Connect
+ switch ($type) {
+ case 'http://axschema.org/namePerson/friendly':
+ if (isset($user['user_info']['nickname'])) return $user['user_info']['nickname'];
+ break;
+ case 'http://axschema.org/contact/email':
+ if (isset($user['user_info']['email'])) return $user['user_info']['email'];
+ break;
+ case 'http://axschema.org/namePerson':
+ if (isset($user['user_info']['name'])) return $user['user_info']['name'];
+ break;
+ case 'http://axschema.org/pref/timezone':
+ if (isset($user['user_info']['zoneinfo'])) return $user['user_info']['zoneinfo'];
+ break;
+ case 'http://axschema.org/person/gender':
+ if (isset($user['user_info']['gender'])) return strtoupper(substr($user['user_info']['gender'], 0, 1));
+ break;
+ case 'http://axschema.org/contact/postalCode/home':
+ if (isset($user['user_info']['address']['postal_code'])) return $user['user_info']['address']['postcal_code'];
+ break;
+ }
+
+ // Look up sreg
+ if (isset($ax_sreg_map[$type]) && isset($user['sreg'][$ax_sreg_map[$type]])) {
+ return $user['sreg'][$ax_sreg_map[$type]];
+ } else {
+ return NULL;
+ }
+ }
+}
+?>
diff --git a/simpleid/www/extensions/ax/ax.xtpl b/simpleid/www/extensions/ax/ax.xtpl
new file mode 100644
index 0000000..bf9edbe
--- /dev/null
+++ b/simpleid/www/extensions/ax/ax.xtpl
@@ -0,0 +1,44 @@
+<!-- BEGIN: user_page -->
+<!-- :mode=html: $Id$ -->
+ <p>{ax_data}</p>
+
+ <p>{edit_identity_file}</p>
+
+ <table>
+ <tr>
+ <th>{name_label}</th>
+ <th>{value_label}</th>
+ </tr>
+ <!-- BEGIN: ax -->
+ <tr>
+ <td>{name}</td>
+ <td>{value}</td>
+ </tr>
+ <!-- END: ax -->
+ </table>
+<!-- END: user_page -->
+
+<!-- BEGIN: fetch_request -->
+ <p>{ax_data}</p>
+
+ <table>
+ <tr>
+ <th></th>
+ <th>{name_label}</th>
+ <th>{value_label}</th>
+ </tr>
+ <!-- BEGIN: ax -->
+ <tr>
+ <td><input name="ax_consents[]" type="checkbox" value="{name}" id="ax-consents-{id}" {checked} {disabled} /><!-- BEGIN: required --><input name="ax_consents[]" type="hidden" value="{name}" /><!-- END: required --></td>
+ <td><label for="ax-consents-{id}">{name}</label></td>
+ <td>{value}</td>
+ </tr>
+ <!-- END: ax -->
+ </table>
+<!-- END: fetch_request -->
+
+<!-- BEGIN: store_request -->
+<div class="message">
+ <p>{store_request_message}</p>
+</div>
+<!-- END: store_request -->
diff --git a/simpleid/www/extensions/certauth/certauth.extension.php b/simpleid/www/extensions/certauth/certauth.extension.php
new file mode 100644
index 0000000..b442d93
--- /dev/null
+++ b/simpleid/www/extensions/certauth/certauth.extension.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2012
+ *
+ * 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$
+ */
+
+/**
+ * Authentication using a SSL client certificate.
+ *
+ * @package simpleid
+ * @subpackage extensions
+ * @filesource
+ */
+
+
+/**
+ * Attempt to login using a SSL client certificate.
+ *
+ * Note that the web server must be set up to request a SSL client certificate
+ * and pass the certificate's details to PHP.
+ */
+function certauth_user_auto_login() {
+ if (!_certauth_has_client_cert()) return NULL;
+
+ $cert = trim($_SERVER['SSL_CLIENT_M_SERIAL']) . ';' . trim($_SERVER['SSL_CLIENT_I_DN']);
+ log_debug('Client SSL certificate: ' . $cert);
+
+ $uid = store_get_uid_from_cert($cert);
+ if ($uid != NULL) {
+ log_debug('Client SSL certificate accepted for ' . $uid);
+ return user_load($uid);
+ } else {
+ log_warn('Client SSL certificate presented, but no user with that certificate exists.');
+ return NULL;
+ }
+}
+
+/**
+ * Determines whether the user agent supplied valid a certificate identifying the
+ * user.
+ *
+ * A valid certificate is supplied if all of the following occurs:
+ *
+ * - the connection is done using HTTPS (i.e. {@link is_https()} is true)
+ * - the web server has been set up to request a certificate from the user agent
+ * - the web server has been set up to pass the certificate details to PHP
+ * - the certificate has not been revoked
+ * - the certificate contains a serial number and a valid issuer
+ *
+ * @return true if the user agent has supplied a valid SSL certificate
+ */
+function _certauth_has_client_cert() {
+ // False if we are not in HTTP
+ if (!is_https()) return false;
+
+ // False if certificate is not valid
+ if (!isset($_SERVER['SSL_CLIENT_VERIFY']) || ($_SERVER['SSL_CLIENT_VERIFY'] !== 'SUCCESS')) return false;
+
+ // False if certificate is expired or has no expiry date
+ if (!isset($_SERVER['SSL_CLIENT_V_REMAIN']) || ($_SERVER['SSL_CLIENT_V_REMAIN'] < 0)) return false;
+ if (!isset($_SERVER['SSL_CLIENT_V_END'])) return false;
+
+ // False if no serial number
+ if (!isset($_SERVER['SSL_CLIENT_M_SERIAL'])) return false;
+
+ // False if no issuer
+ if (!isset($_SERVER['SSL_CLIENT_I_DN'])) return false;
+
+ return true;
+}
+?>
diff --git a/simpleid/www/extensions/pape/pape.extension.php b/simpleid/www/extensions/pape/pape.extension.php
new file mode 100644
index 0000000..e189f2e
--- /dev/null
+++ b/simpleid/www/extensions/pape/pape.extension.php
@@ -0,0 +1,200 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Implements the Provider Authentication Policy Extension extension.
+ *
+ *
+ * @package simpleid
+ * @subpackage extensions
+ * @filesource
+ */
+
+/** Namespace for the PAPE extension */
+define('OPENID_NS_PAPE', 'http://specs.openid.net/extensions/pape/1.0');
+
+/** Namespaces for PAPE policies */
+define('PAPE_POLICY_NONE', 'http://schemas.openid.net/pape/policies/2007/06/none');
+define('PAPE_POLICY_PPID', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier');
+
+/** Namespaces for PAPE levels */
+define('PAPE_LEVEL_NIST800_63', 'http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf');
+
+/**
+ * Returns the support for PAPE in SimpleID XRDS document
+ *
+ * @return array
+ * @see hook_xrds_types()
+ */
+function pape_xrds_types() {
+ return array(
+ OPENID_NS_PAPE,
+ PAPE_POLICY_PPID,
+ PAPE_LEVEL_NIST800_63
+ );
+}
+
+/**
+ * @see hook_checkid_identity()
+ */
+function pape_checkid_identity($request, $identity, $immediate) {
+ global $user;
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_PAPE, $request)) return null;
+
+ // See if we are choosing an identity and save for later
+ // This may be used by pape_response() to produce a private identifier
+ if ($request['openid.identity'] == OPENID_IDENTIFIER_SELECT) _pape_identifier_select(true);
+
+ $pape_request = openid_extension_filter_request(OPENID_NS_PAPE, $request);
+
+ // If the relying party provides a max_auth_age
+ if (isset($pape_request['max_auth_age'])) {
+ // If we are not logged in then we don't need to do anything
+ if ($user == NULL) return NULL;
+
+ // If the last time we logged on actively (i.e. using a password) is greater than
+ // max_auth_age, we then require the user to log in again
+ if ((!isset($user['auth_active']) || !$user['auth_active'])
+ && ((time() - $user['auth_time']) > $pape_request['max_auth_age'])) {
+ set_message(t('This web site\'s policy requires you to log in again to confirm your identity.'));
+
+ _user_logout();
+ return CHECKID_LOGIN_REQUIRED;
+ }
+ }
+}
+
+/**
+ * @see hook_response()
+ */
+function pape_response($assertion, $request) {
+ global $user, $version;
+
+ // We only deal with positive assertions
+ if (!$assertion) return array();
+
+ // We only respond if we are using OpenID 2 or later
+ if ($version < OPENID_VERSION_2) return array();
+
+ // Get what is requested
+ $pape_request = openid_extension_filter_request(OPENID_NS_PAPE, $request);
+
+ // If the extension is requested, we use the same alias, otherwise, we
+ // make one up
+ $alias = openid_extension_alias(OPENID_NS_PAPE, 'pape');
+ $response = array();
+
+ // The PAPE specification recommends us to respond even when the extension
+ // is not present in the request.
+ $response['openid.ns.' . $alias] = OPENID_NS_PAPE;
+
+ // We return the last time the user logged in using the login form
+ $response['openid.' . $alias . '.auth_time'] = gmstrftime('%Y-%m-%dT%H:%M:%SZ', $user['auth_time']);
+
+ // We don't comply with NIST_SP800-63
+ $response['openid.' . $alias . '.auth_level.ns.nist'] = PAPE_LEVEL_NIST800_63;
+ $response['openid.' . $alias . '.auth_level.nist'] = 0;
+
+ // The default is that we don't apply any authentication policies. This can be changed later in the
+ // function
+ $response['openid.' . $alias . '.auth_policies'] = PAPE_POLICY_NONE;
+
+ // Now we go through the authentication policies
+ if (isset($pape_request['preferred_auth_policies'])) {
+ $policies = preg_split('/\s+/', $pape_request['preferred_auth_policies']);
+
+ if (in_array(PAPE_POLICY_PPID, $policies)) {
+ // We want a ppid. Check that the authentication request is correct
+ if (_pape_identifier_select()) {
+ $realm = openid_get_realm($request, $version);
+ $identity = $request['openid.identity'];
+
+ $ppid = _pape_ppid($identity, $realm);
+ $response['openid.claimed_id'] = $ppid;
+ $response['openid.identity'] = $ppid;
+ }
+ }
+ }
+
+ return $response;
+}
+
+/**
+ * Returns an array of fields that need signing.
+ *
+ * @see hook_signed_fields()
+ */
+function pape_signed_fields($response) {
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_PAPE, $response));
+ $alias = openid_extension_alias(OPENID_NS_PAPE);
+ $signed_fields = array();
+
+ if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias;
+ foreach ($fields as $field) {
+ if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field;
+ }
+
+ return $signed_fields;
+}
+
+/**
+ * Sets and returns whether the current OpenID request is requesting an identity.
+ *
+ * @param bool $identifier_select
+ * @return bool whether the current OpenID request is requesting an identity
+ */
+function _pape_identifier_select($identifier_select = NULL) {
+ static $static_identifier_select = false;
+
+ if (!is_null($identifier_select)) $static_identifier_select = $identifier_select;
+
+ return $static_identifier_select;
+}
+
+/**
+ * Generates a private personal identifier (PPID). The PPID is an opaque identifier
+ * for a particular user-RP pair
+ *
+ * @param string $identity the identity of the user
+ * @param string $realm the URL of the relying party
+ * @return string the PPID
+ */
+function _pape_ppid($identity, $realm) {
+ // We are reusing the site-token from get_form_token() in common.inc
+ if (store_get('site-token') == NULL) {
+ $site_token = mt_rand();
+ store_set('site-token', $site_token);
+ } else {
+ $site_token = store_get('site-token');
+ }
+
+ $parts = parse_url($realm);
+ $host = $parts['host'];
+ if (strstr($host, 'www.') === 0) $host = substr($host, 4);
+
+ return simpleid_url('ppid/' . md5($site_token . $identity . $host));
+}
+
+?>
diff --git a/simpleid/www/extensions/sreg/sreg.extension.php b/simpleid/www/extensions/sreg/sreg.extension.php
new file mode 100644
index 0000000..955d6f6
--- /dev/null
+++ b/simpleid/www/extensions/sreg/sreg.extension.php
@@ -0,0 +1,240 @@
+<?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$
+ */
+
+/**
+ * Implements the Simple Registration extension.
+ *
+ *
+ * @package simpleid
+ * @subpackage extensions
+ * @filesource
+ */
+
+/** Namespace for the Simple Registration extension */
+define('OPENID_NS_SREG', 'http://openid.net/extensions/sreg/1.1');
+
+/**
+ * @see hook_response()
+ */
+function sreg_response($assertion, $request) {
+ global $user;
+ global $version;
+
+ // We only deal with positive assertions
+ if (!$assertion) return array();
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_SREG, $request)) return array();
+
+ $request = openid_extension_filter_request(OPENID_NS_SREG, $request);
+ $required = (isset($request['required'])) ? explode(',', $request['required']) : array();
+ $optional = (isset($request['optional'])) ? explode(',', $request['optional']) : array();
+ $fields = array_merge($required, $optional);
+ $alias = openid_extension_alias(OPENID_NS_SREG);
+ $response = array();
+
+ if ($version == OPENID_VERSION_2) $response['openid.ns.' . $alias] = OPENID_NS_SREG;
+
+ foreach ($fields as $field) {
+ $value = _sreg_get_value($field);
+
+ if ($value != NULL) $response['openid.' . $alias . '.' . $field] = $value;
+ }
+
+ return $response;
+}
+
+/**
+ * Returns an array of fields that need signing.
+ *
+ * @see hook_signed_fields()
+ */
+function sreg_signed_fields($response) {
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_SREG, $response)) return array();
+
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_SREG, $response));
+ $alias = openid_extension_alias(OPENID_NS_SREG);
+ $signed_fields = array();
+
+ if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias;
+ foreach ($fields as $field) {
+ if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field;
+ }
+
+ return $signed_fields;
+}
+
+/**
+ * @see hook_consent_form()
+ */
+function sreg_consent_form($request, $response, $rp) {
+ global $user;
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_SREG, $request)) return '';
+
+ $request = openid_extension_filter_request(OPENID_NS_SREG, $request);
+ $required = (isset($request['required'])) ? explode(',', $request['required']) : array();
+ $optional = (isset($request['optional'])) ? explode(',', $request['optional']) : array();
+ $fields = array_merge($required, $optional);
+
+ if ((count($request)) && isset($user['sreg'])) {
+ $xtpl2 = new XTemplate('extensions/sreg/sreg.xtpl');
+
+ $xtpl2->assign('alias', openid_extension_alias(OPENID_NS_SREG));
+
+ if (isset($request['policy_url'])) {
+ $xtpl2->assign('policy', t('You can view the site\'s policy in relation to the use of this information at this URL: <a href="@url">@url</a>.', array('@url' => $request['policy_url'])));
+ }
+
+ foreach ($fields as $field) {
+ $value = _sreg_get_value($field);
+
+ if ($value != NULL) {
+ $xtpl2->assign('name', htmlspecialchars($field, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
+
+ $xtpl2->assign('checked', (in_array($field, $required) || !isset($rp['sreg_consents']) || in_array($field, $rp['sreg_consents'])) ? 'checked="checked"' : '');
+ $xtpl2->assign('disabled', (in_array($field, $required)) ? 'disabled="disabled"' : '');
+ if (in_array($field, $required)) $xtpl2->parse('form.sreg.required');
+
+ $xtpl2->parse('form.sreg');
+ }
+ }
+
+ $xtpl2->assign('sreg_data', t('SimpleID will also be sending the following registration information to the site.'));
+ $xtpl2->assign('name_label', t('Name'));
+ $xtpl2->assign('value_label', t('Value'));
+
+ $xtpl2->parse('form');
+ return $xtpl2->text('form');
+ }
+}
+
+/**
+ * @see hook_consent()
+ */
+function sreg_consent($form_request, &$response, &$rp) {
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_SREG, $response)) return;
+
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_SREG, $response));
+ $alias = openid_extension_alias(OPENID_NS_SREG);
+
+ foreach ($fields as $field) {
+ if (isset($response['openid.' . $alias . '.' . $field])) {
+ if (!in_array($field, $form_request['sreg_consents'])) {
+ unset($response['openid.' . $alias . '.' . $field]);
+ }
+ }
+ }
+
+ if (count(array_keys(openid_extension_filter_request(OPENID_NS_SREG, $response))) == 0) {
+ // We have removed all the responses, so we remove the namespace as well
+ unset($response['openid.ns.' . $alias]);
+ }
+
+ $rp['sreg_consents'] = $form_request['sreg_consents'];
+}
+
+/**
+ * @see hook_page_profile()
+ */
+function sreg_page_profile() {
+ global $user;
+ $xtpl2 = new XTemplate('extensions/sreg/sreg.xtpl');
+
+ if (isset($user['sreg'])) {
+ foreach ($user['sreg'] as $name => $value) {
+ $xtpl2->assign('name', htmlspecialchars($name, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
+ $xtpl2->parse('user_page.sreg');
+ }
+ }
+
+ $xtpl2->assign('sreg_data', t('SimpleID will send the following information to sites which supports the Simple Registration Extension.'));
+ $xtpl2->assign('connect_data', t('If you have also supplied OpenID Connect user information in your identity file, these may also be sent as part of this Extension.'));
+ $xtpl2->assign('edit_identity_file', t('To change these, <a href="!url">edit your identity file</a>.', array('!url' => 'http://simpleid.org/docs/1/identity-files/')));
+ $xtpl2->assign('name_label', t('Name'));
+ $xtpl2->assign('value_label', t('Value'));
+
+ $xtpl2->parse('user_page');
+
+ return array(array(
+ 'id' => 'sreg',
+ 'title' => t('Simple Registration Extension'),
+ 'content' => $xtpl2->text('user_page')
+ ));
+}
+
+
+/**
+ * Looks up the value of a specified Simple Registration Extension field.
+ *
+ * This function looks up the sreg section of the user's identity file. If the
+ * specified field cannot be found, it looks up the corresponding field in the
+ * OpenID Connect user information (user_info section).
+ *
+ * @param string $field the field to look up
+ * @return string the value or NULL if not found
+ */
+function _sreg_get_value($field) {
+ global $user;
+
+ if (isset($user['sreg'][$field])) {
+ return $user['sreg'][$field];
+ } else {
+ switch ($field) {
+ case 'nickname':
+ case 'email':
+ if (isset($user['user_info'][$field])) return $user['user_info'][$field];
+ break;
+ case 'fullname':
+ if (isset($user['user_info']['name'])) return $user['user_info']['name'];
+ break;
+ case 'timezone':
+ if (isset($user['user_info']['zoneinfo'])) return $user['user_info']['zoneinfo'];
+ break;
+ case 'gender':
+ if (isset($user['user_info']['gender'])) return strtoupper(substr($user['user_info']['gender'], 0, 1));
+ break;
+ case 'postcode':
+ if (isset($user['user_info']['address']['postal_code'])) return $user['user_info']['address']['postcal_code'];
+ break;
+ default:
+ return NULL;
+ }
+ return NULL;
+ }
+}
+
+
+
+?>
diff --git a/simpleid/www/extensions/sreg/sreg.xtpl b/simpleid/www/extensions/sreg/sreg.xtpl
new file mode 100644
index 0000000..07550bb
--- /dev/null
+++ b/simpleid/www/extensions/sreg/sreg.xtpl
@@ -0,0 +1,40 @@
+<!-- BEGIN: user_page -->
+<!-- :mode=html: $Id$ -->
+ <p>{sreg_data}</p>
+
+ <p>{connect_data}</p>
+
+ <p>{edit_identity_file}</p>
+
+ <table>
+ <tr>
+ <th>{name_label}</th>
+ <th>{value_label}</th>
+ </tr>
+ <!-- BEGIN: sreg -->
+ <tr>
+ <td>{name}</td>
+ <td>{value}</td>
+ </tr>
+ <!-- END: sreg -->
+ </table>
+<!-- END: user_page -->
+
+<!-- BEGIN: form -->
+ <p>{sreg_data} {policy}</p>
+
+ <table>
+ <tr>
+ <th></th>
+ <th>{name_label}</th>
+ <th>{value_label}</th>
+ </tr>
+ <!-- BEGIN: sreg -->
+ <tr>
+ <td><input name="sreg_consents[]" type="checkbox" value="{name}" id="sreg-consents-{name}" {checked} {disabled} /><!-- BEGIN: required --><input name="sreg_consents[]" type="hidden" value="{name}" /><!-- END: required --></td>
+ <td><input type="hidden" name="openid.{alias}.{name}" value="{value}" /><label for="sreg-consents-{name}">{name}</label></td>
+ <td>{value}</td>
+ </tr>
+ <!-- END: sreg -->
+ </table>
+<!-- END: form -->
diff --git a/simpleid/www/extensions/ui/ui.css b/simpleid/www/extensions/ui/ui.css
new file mode 100644
index 0000000..a6b9cfd
--- /dev/null
+++ b/simpleid/www/extensions/ui/ui.css
@@ -0,0 +1,31 @@
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+.dialog-page {
+ background: #FFFFFF;
+}
+
+.dialog-page #content {
+ margin: 10px auto;
+ padding: 0;
+ border: 0;
+}
diff --git a/simpleid/www/extensions/ui/ui.extension.php b/simpleid/www/extensions/ui/ui.extension.php
new file mode 100644
index 0000000..3982be0
--- /dev/null
+++ b/simpleid/www/extensions/ui/ui.extension.php
@@ -0,0 +1,260 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Implements the popup and icon modes from the User Interface extension
+ *
+ * @package simpleid
+ * @subpackage extensions
+ * @filesource
+ */
+
+/** Namespace for the User Interface extension */
+define('OPENID_NS_UI', 'http://specs.openid.net/extensions/ui/1.0');
+
+/**
+ * Returns the popup mode in SimpleID XRDS document
+ *
+ * @return array
+ * @see hook_xrds_types()
+ */
+function ui_xrds_types() {
+ return array(
+ 'http://specs.openid.net/extensions/ui/1.0/mode/popup',
+ 'http://specs.openid.net/extensions/ui/1.0/icon'
+ );
+}
+
+/**
+ * Detects the openid.ui.x-has-session parameter and processes it accordingly.
+ *
+ * @return array
+ * @see hook_response()
+ */
+function ui_response($assertion, $request) {
+ global $user;
+ global $version;
+
+ // We only deal with negative assertions
+ if ($assertion) return array();
+
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_UI, $request)) return array();
+
+ // We only deal with openid.ui.x-has-session requests
+ $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request);
+ if (!isset($filtered_request['mode']) || ($filtered_request['mode'] != 'x-has-session')) return array();
+
+ // If user is null, there is no active session
+ if ($user == NULL) return array();
+
+ // There is an active session
+ $alias = openid_extension_alias(OPENID_NS_UI);
+ $response = array();
+
+ $response['openid.ns.' . $alias] = OPENID_NS_UI;
+ $response['openid.' . $alias . '.mode'] = 'x-has-session';
+
+ return $response;
+}
+
+/**
+ * Returns an array of fields that need signing.
+ *
+ * @see hook_signed_fields()
+ */
+function ui_signed_fields($response) {
+ // We only respond if the extension is requested
+ if (!openid_extension_requested(OPENID_NS_UI, $response)) return array();
+
+ $fields = array_keys(openid_extension_filter_request(OPENID_NS_UI, $response));
+ $alias = openid_extension_alias(OPENID_NS_UI);
+ $signed_fields = array();
+
+ if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias;
+ foreach ($fields as $field) {
+ if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field;
+ }
+
+ return $signed_fields;
+}
+
+/**
+ * Detects the presence of the UI extension and modifies the login form
+ * accordingly.
+ *
+ * @param string $destination
+ * @param string $state
+ * @see hook_user_login_form()
+ */
+function ui_user_login_form($destination, $state) {
+ if (($destination != 'continue') || (!$state)) return;
+
+ $request = unpickle($state);
+ openid_parse_request($request);
+
+ // Skip if popup does not exist
+ if (!openid_extension_requested(OPENID_NS_UI, $request)) return;
+
+ $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request);
+
+ if (isset($filtered_request['mode']) && ($filtered_request['mode'] == 'popup')) _ui_insert_css_js();
+
+ return;
+}
+
+/**
+ * Detects the presence of the UI extension and modifies the relying party
+ * verification form accordingly.
+ *
+ * @param array $request
+ * @param array $response
+ * @param array $rp
+ * @return string
+ * @see hook_consent_form()
+ */
+function ui_consent_form($request, $response, $rp) {
+ // Skip if popup does not exist
+ if (!openid_extension_requested(OPENID_NS_UI, $request)) return '';
+
+ $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request);
+
+ if (isset($filtered_request['mode']) && ($filtered_request['mode'] == 'popup')) _ui_insert_css_js();
+
+ if (isset($filtered_request['icon']) && ($filtered_request['icon'] == 'true')) {
+ global $xtpl;
+
+ $realm = $request['openid.realm'];
+ $icon_url = simpleid_url('ui/icon', 'realm=' . rawurlencode($realm) . '&tk=' . _ui_icon_token($realm));
+
+ $xtpl->assign('icon_url', htmlspecialchars($icon_url, ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('main.openid_consent.icon');
+ }
+
+ return '';
+}
+
+/**
+ * Specifies that the OpenID response should be sent via the fragment
+ *
+ */
+function ui_indirect_response($url, $response) {
+ global $openid_ns_to_alias;
+ if (!array_key_exists(OPENID_NS_UI, $openid_ns_to_alias)) return NULL;
+
+ // Cheat - if we run this, then the redirect page will also be themed!
+ _ui_insert_css_js();
+
+ if (strstr($url, '#')) {
+ return OPENID_RESPONSE_FRAGMENT;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Adds an extra route to the SimpleWeb framework.
+ */
+function ui_routes() {
+ return array('ui/icon' => 'ui_icon');
+}
+
+/**
+ * Returns an icon.
+ */
+function ui_icon() {
+ if (!isset($_GET['realm']) || !isset($_GET['tk']) || ($_GET['tk'] != _ui_icon_token($_GET['realm']))) {
+ header_response_code('404 Not Found');
+ indirect_fatal_error(t('Invalid UI icon parameters.'));
+ }
+
+ $realm = $_GET['realm'];
+ $icon_res = _ui_get_icon($realm);
+
+ if ($icon_res === NULL) {
+ header_response_code('404 Not Found');
+ indirect_fatal_error(t('Unable to get icon.'));
+ }
+
+ header('Via: ' . $icon_res['protocol'] . ' simpleid-ui-icon-' . md5($realm));
+ header('Cache-Control: max-age=86400');
+ header('Content-Type: ' . $icon_res['headers']['content-type']);
+ if (isset($icon_res['headers']['content-encoding'])) header('Content-Encoding: ' . $icon_res['headers']['content-encoding']);
+ print $icon_res['data'];
+}
+
+/**
+ * Inserts the necessary CSS and JavaScript code to implement the popup mode
+ * from the User Interface extension.
+ */
+function _ui_insert_css_js() {
+ global $xtpl;
+
+ $css = (isset($xtpl->vars['css'])) ? $xtpl->vars['css'] : '';
+ $js = (isset($xtpl->vars['javascript'])) ? $xtpl->vars['javascript'] : '';
+
+ $xtpl->assign('css', $css . '@import url(' . get_base_path() . 'extensions/ui/ui.css);');
+ $xtpl->assign('javascript', $js . '<script src="' . get_base_path() . 'extensions/ui/ui.js" type="text/javascript"></script>');
+}
+
+/**
+ * Attempts to obtain an icon from a RP
+ *
+ * @param string $realm the openid.realm parameter
+ * @return array the response from {@link http_make_request()} with the discovered URL of the
+ * RP's icon
+ */
+function _ui_get_icon($realm) {
+ $rp_info = simpleid_get_rp_info($realm);
+
+ if (isset($rp_info['ui_icon'])) return $rp_info['ui_icon'];
+
+ $services = discovery_xrds_services_by_type($rp_info['services'], 'http://specs.openid.net/extensions/ui/icon');
+
+ if ($services) {
+ $icon_url = $services[0]['uri'];
+
+ $icon_res = http_make_request($icon_url);
+ if (isset($icon_res['http-error'])) {
+ return NULL;
+ }
+
+ $rp_info['ui_icon'] = $icon_res;
+ simpleid_set_rp_info($realm, $rp_info);
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Returns a token to be used when requesting the icon.
+ *
+ * The token is used to prevent flooding SimpleID with external requests.
+ *
+ * @param string $realm the openid.realm parameter
+ * @return string the token
+ */
+function _ui_icon_token($realm) {
+ return get_form_token('q=ui/icon&realm=' . rawurlencode($realm));
+}
+?>
diff --git a/simpleid/www/extensions/ui/ui.js b/simpleid/www/extensions/ui/ui.js
new file mode 100644
index 0000000..1711d13
--- /dev/null
+++ b/simpleid/www/extensions/ui/ui.js
@@ -0,0 +1,22 @@
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * This program is licensed under the GPL.
+ *
+ * $Id$
+ */
+
+$(document).ready(function() {
+ $('input#edit-cancel').click(function() {
+ window.close();
+ return false;
+ });
+ $(document).keydown(function(e) {
+ if (e.which == 27) { // Close the window if user presses Esc
+ window.close();
+ return false;
+ }
+ });
+});
diff --git a/simpleid/www/filesystem.store.php b/simpleid/www/filesystem.store.php
new file mode 100644
index 0000000..7d99a8d
--- /dev/null
+++ b/simpleid/www/filesystem.store.php
@@ -0,0 +1,334 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-9
+ *
+ * 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$
+ */
+
+/**
+ * Functions for persistent storage via the file system.
+ *
+ * In general, there are three different sets of data which SimpleID needs
+ * to store:
+ *
+ * - transient data (e.g. OpenID associations, sessions, auto-login)
+ * - application data (e.g. salt for form tokens)
+ * - user data (e.g. user names, passwords and settings)
+ *
+ * Prior to version 0.7, both transient data and application data are stored
+ * using {@link cache.inc}. From version 0.7, application data are now
+ * stored separately from the cache.
+ *
+ * Prior to version 0.7, user data is only stored in the identity file, to which
+ * SimpleID cannot write. This means that if the user wishes to change a setting,
+ * he or she will need to edit the identity file manually. Other user settings
+ * (e.g. RP preferences) are stored using {@link cache.inc}
+ *
+ * From version 0.7, user data is stored in two files, one is the identity
+ * file, the other is the user store file, which SimpleID can write.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+/**
+ * This variable is a cache of SimpleID's application settings. It is populated
+ * progressively as {@link store_get()} is called.
+ *
+ * @global array $simpleid_settings
+ */
+$simpleid_settings = array();
+
+/**
+ * Returns whether the user name exists in the user store.
+ *
+ * @param string $uid the name of the user to check
+ * @return bool whether the user name exists
+ */
+function store_user_exists($uid) {
+ if (_store_is_valid_name($uid)) {
+ $identity_file = SIMPLEID_IDENTITIES_DIR . "/$uid.identity";
+ return (file_exists($identity_file));
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Loads user data for a specified user name.
+ *
+ * The user name must exist. You should check whether the user name exists with
+ * the {@link store_user_exists()} function
+ *
+ * @param string $uid the name of the user to load
+ * @return mixed data for the specified user
+ */
+function store_user_load($uid) {
+ if (!_store_is_valid_name($uid)) return array();
+ $store_file = SIMPLEID_STORE_DIR . "/$uid.usrstore";
+
+ if (file_exists($store_file)) {
+ $data = unserialize(file_get_contents($store_file));
+ } else {
+ $data = array();
+ }
+
+ $identity_file = SIMPLEID_IDENTITIES_DIR . "/$uid.identity";
+ $data = array_merge($data, parse_ini_file($identity_file, TRUE));
+
+ return $data;
+}
+
+/**
+ * Returns the time which a user's data has been updated.
+ *
+ * The user name must exist. You should check whether the user name exists with
+ * the {@link store_user_exists()} function.
+ *
+ * The time returned can be based on the identity file,
+ * the user store file, or the latter of the two.
+ *
+ * @param string $uid the name of the user to obtain the update time
+ * @param string $type one of: 'identity' (identity file), 'usrstore' (user store
+ * file) or NULL (latter of the two)
+ * @return int the updated time
+ */
+function store_user_updated_time($uid, $type = NULL) {
+ if (!_store_is_valid_name($uid)) return NULL;
+
+ $identity_file = SIMPLEID_IDENTITIES_DIR . "/$uid.identity";
+ $identity_time = filemtime($identity_file);
+
+ $store_file = SIMPLEID_STORE_DIR . "/$uid.usrstore";
+ if (file_exists($store_file)) {
+ $store_time = filemtime($store_file);
+ } else {
+ $store_time = NULL;
+ }
+
+ if ($type == 'identity') {
+ return $identity_time;
+ } elseif ($type == 'usrstore') {
+ return $store_time;
+ } elseif ($type == NULL) {
+ return ($identity_time > $store_time) ? $identity_time : $store_time;
+ } else {
+ return NULL;
+ }
+}
+
+
+/**
+ * Finds the user name from a specified OpenID Identity URI.
+ *
+ * @param string $identity the Identity URI of the user to load
+ * @return string the user name for the Identity URI, or NULL if no user has
+ * the specified Identity URI
+ */
+function store_get_uid($identity) {
+ $uid = cache_get('identity', $identity);
+ if ($uid !== NULL) return $uid;
+
+ $r = NULL;
+
+ $dir = opendir(SIMPLEID_IDENTITIES_DIR);
+
+ while (($file = readdir($dir)) !== false) {
+ $filename = SIMPLEID_IDENTITIES_DIR . '/' . $file;
+
+ if (is_link($filename)) $filename = readlink($filename);
+ if ((filetype($filename) != "file") || (!preg_match('/^(.+)\.identity$/', $file, $matches))) continue;
+
+ $uid = $matches[1];
+ $test_user = store_user_load($uid);
+
+ cache_set('identity', $test_user['identity'], $uid);
+
+ if ($test_user['identity'] == $identity) {
+ $r = $uid;
+ }
+ }
+
+ closedir($dir);
+
+ return $r;
+}
+
+/**
+ * Finds the user name from a specified client SSL certificate string.
+ *
+ * The client SSL certificate string comprises the certificate's serial number
+ * (in capitals hex notation) and the distinguished name of the certificate's issuer
+ * (with components joined using slashes), joined using a semi-colon.
+ *
+ *
+ * @param string $cert the client SSL certificate string of the user to load
+ * @return string the user name matching the client SSL certificate string, or NULL if no user has
+ * client SSL certificate string
+ */
+function store_get_uid_from_cert($cert) {
+ $uid = cache_get('cert', $cert);
+ if ($uid !== NULL) return $uid;
+
+ $r = NULL;
+
+ $dir = opendir(SIMPLEID_IDENTITIES_DIR);
+
+ while (($file = readdir($dir)) !== false) {
+ $filename = SIMPLEID_IDENTITIES_DIR . '/' . $file;
+
+ if (is_link($filename)) $filename = readlink($filename);
+ if ((filetype($filename) != "file") || (!preg_match('/^(.+)\.identity$/', $file, $matches))) continue;
+
+ $uid = $matches[1];
+ $test_user = store_user_load($uid);
+
+ if (isset($test_user['certauth']['cert'])) {
+ if (is_array($test_user['certauth']['cert'])) {
+ foreach ($test_user['certauth']['cert'] as $test_cert) {
+ if (trim($test_cert) != '') cache_set('cert', $test_cert, $uid);
+ }
+ foreach ($test_user['certauth']['cert'] as $test_cert) {
+ if ((trim($test_cert) != '') && ($test_cert == $cert)) $r = $uid;
+ }
+ } else {
+ if (trim($test_cert) != '') {
+ cache_set('cert', $test_user['certauth']['cert'], $uid);
+ if ($test_user['certauth']['cert'] == $cert) $r = $uid;
+ }
+ }
+ }
+ }
+
+ closedir($dir);
+
+ return $r;
+}
+
+/**
+ * Saves user data for a specific user name.
+ *
+ * This data is stored in the user store file.
+ *
+ * @param string $uid the name of the user
+ * @param array $data the data to save
+ * @param array $exclude an array of keys to exclude from the user store file.
+ * These are generally keys which are stored in the identity file.
+ *
+ * @since 0.7
+ */
+function store_user_save($uid, $data, $exclude = array()) {
+ foreach ($exclude as $key) {
+ if (isset($data[$key])) unset($data[$key]);
+ }
+
+ if (!_store_is_valid_name($uid)) {
+ trigger_error("Invalid user name for filesystem store", E_USER_ERROR);
+ return;
+ }
+
+ $store_file = SIMPLEID_STORE_DIR . "/$uid.usrstore";
+ $file = fopen($store_file, 'w');
+ fwrite($file, serialize($data));
+ fclose($file);
+}
+
+/**
+ * Loads an application setting.
+ *
+ * @param string $name the name of the setting to return
+ * @param mixed $default the default value to use if this variable has never been set
+ * @return mixed the value of the setting
+ *
+ */
+function store_get($name, $default = NULL) {
+ global $simpleid_settings;
+
+ if (!_store_is_valid_name($name)) return $default;
+
+ if (!isset($simpleid_settings[$name])) {
+ $setting_file = SIMPLEID_STORE_DIR . "/$name.setting";
+
+ if (file_exists($setting_file)) {
+ $simpleid_settings[$name] = unserialize(file_get_contents($setting_file));
+ } else {
+ return $default;
+ }
+ }
+
+ return $simpleid_settings[$name];
+}
+
+/**
+ * Saves an application setting.
+ *
+ * @param string $name the name of the setting to save
+ * @param mixed $value the value of the setting
+ *
+ */
+function store_set($name, $value) {
+ global $simpleid_settings;
+
+ if (!_store_is_valid_name($name)) {
+ trigger_error("Invalid setting name for filesystem store", E_USER_ERROR);
+ return;
+ }
+
+ $simpleid_settings[$name] = $value;
+
+ $setting_file = SIMPLEID_STORE_DIR . "/$name.setting";
+ $file = fopen($setting_file, 'w');
+ fwrite($file, serialize($value));
+ fclose($file);
+}
+
+/**
+ * Deletes an application setting.
+ *
+ * @param string $name the name of the setting to delete
+ *
+ */
+function store_del($name) {
+ global $simpleid_settings;
+
+ if (!_store_is_valid_name($name)) {
+ trigger_error("Invalid setting name for filesystem store", E_USER_ERROR);
+ return;
+ }
+
+ if (isset($simpleid_settings[$name])) unset($simpleid_settings[$name]);
+
+ $setting_file = SIMPLEID_STORE_DIR . "/$name.setting";
+ if (file_exists($setting_file)) unlink($setting_file);
+}
+
+/**
+ * Determines whether a name is a valid name for use with this store.
+ *
+ * For file system storage, a name is not valid if it contains either a
+ * directory separator (i.e. / or \).
+ *
+ * @param string $name the name to check
+ * @return boolean whether the name is valid for use with this store
+ *
+ */
+function _store_is_valid_name($name) {
+ return preg_match('!\A[^/\\\\]*\z!', $name);
+}
+?>
diff --git a/simpleid/www/html/application.png b/simpleid/www/html/application.png
new file mode 100644
index 0000000..1dee9e3
--- /dev/null
+++ b/simpleid/www/html/application.png
Binary files differ
diff --git a/simpleid/www/html/block.xtpl b/simpleid/www/html/block.xtpl
new file mode 100644
index 0000000..c70ff41
--- /dev/null
+++ b/simpleid/www/html/block.xtpl
@@ -0,0 +1,16 @@
+<!-- :mode=html: $Id$ -->
+<!-- BEGIN: block -->
+<div class="block" id="{id}">
+ <div class="block-header">
+ <!-- BEGIN: links -->
+ <div class="block-header-links">
+ {links}
+ </div>
+ <!-- END: links -->
+ <h2>{title}</h2>
+ </div>
+ <div class="block-content">
+ {content}
+ </div>
+</div>
+<!-- END: block -->
diff --git a/simpleid/www/html/drive.png b/simpleid/www/html/drive.png
new file mode 100644
index 0000000..37b7c9b
--- /dev/null
+++ b/simpleid/www/html/drive.png
Binary files differ
diff --git a/simpleid/www/html/jquery.js b/simpleid/www/html/jquery.js
new file mode 100644
index 0000000..da41706
--- /dev/null
+++ b/simpleid/www/html/jquery.js
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.2.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t
+}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
+u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/simpleid/www/html/jquery.qrcode.js b/simpleid/www/html/jquery.qrcode.js
new file mode 100644
index 0000000..fe9680e
--- /dev/null
+++ b/simpleid/www/html/jquery.qrcode.js
@@ -0,0 +1,28 @@
+(function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;d<a.length&&0==a[d];)d++;this.num=Array(a.length-d+c);for(var b=0;b<a.length-d;b++)this.num[b]=a[b+d]}function p(a,c){this.totalCount=a;this.dataCount=c}function t(){this.buffer=[];this.length=0}u.prototype={getLength:function(){return this.data.length},
+write:function(a){for(var c=0;c<this.data.length;c++)a.put(this.data.charCodeAt(c),8)}};o.prototype={addData:function(a){this.dataList.push(new u(a));this.dataCache=null},isDark:function(a,c){if(0>a||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e<c.length;e++)b+=c[e].dataCount;
+for(e=0;e<this.dataList.length;e++)c=this.dataList[e],d.put(c.mode,4),d.put(c.getLength(),j.getLengthInBits(c.mode,a)),c.write(d);if(d.getLengthInBits()<=8*b)break}this.typeNumber=a}this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17;this.modules=Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=Array(this.moduleCount);for(var b=0;b<this.moduleCount;b++)this.modules[d][b]=null}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-
+7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(a,c);7<=this.typeNumber&&this.setupTypeNumber(a);null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList));this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,c){for(var d=-1;7>=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
+0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c<this.modules.length;c++)for(var d=1*c,b=0;b<this.modules[c].length;b++){var e=1*b;this.modules[c][b]&&(a.beginFill(0,100),a.moveTo(e,d),a.lineTo(e+1,d),a.lineTo(e+1,d+1),a.lineTo(e,d+1),a.endFill())}return a},
+setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(a=8;a<this.moduleCount-8;a++)null==this.modules[6][a]&&(this.modules[6][a]=0==a%2)},setupPositionAdjustPattern:function(){for(var a=j.getPatternPosition(this.typeNumber),c=0;c<a.length;c++)for(var d=0;d<a.length;d++){var b=a[c],e=a[d];if(null==this.modules[b][e])for(var f=-2;2>=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
+j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
+b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0<i;i-=2)for(6==i&&i--;;){for(var g=0;2>g;g++)if(null==this.modules[b][i-g]){var n=!1;f<a.length&&(n=1==(a[f]>>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
+c),b=new t,e=0;e<d.length;e++){var f=d[e];b.put(f.mode,4);b.put(f.getLength(),j.getLengthInBits(f.mode,a));f.write(b)}for(e=a=0;e<c.length;e++)a+=c[e].dataCount;if(b.getLengthInBits()>8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
+0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g<c.length;g++){var n=c[g].dataCount,h=c[g].totalCount-n,b=Math.max(b,n),e=Math.max(e,h);f[g]=Array(n);for(var k=0;k<f[g].length;k++)f[g][k]=255&a.buffer[k+d];d+=n;k=j.getErrorCorrectPolynomial(h);n=(new q(f[g],k.getLength()-1)).mod(k);i[g]=Array(k.getLength()-1);for(k=0;k<i[g].length;k++)h=k+n.getLength()-i[g].length,i[g][k]=0<=h?n.get(h):0}for(k=g=0;k<c.length;k++)g+=c[k].totalCount;d=Array(g);for(k=n=0;k<b;k++)for(g=0;g<c.length;g++)k<f[g].length&&
+(d[n++]=f[g][k]);for(k=0;k<e;k++)for(g=0;g<c.length;g++)k<i[g].length&&(d[n++]=i[g][k]);return d};s=4;for(var j={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,
+78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var c=a<<10;0<=j.getBCHDigit(c)-j.getBCHDigit(j.G15);)c^=j.G15<<j.getBCHDigit(c)-j.getBCHDigit(j.G15);return(a<<10|c)^j.G15_MASK},getBCHTypeNumber:function(a){for(var c=a<<12;0<=j.getBCHDigit(c)-
+j.getBCHDigit(j.G18);)c^=j.G18<<j.getBCHDigit(c)-j.getBCHDigit(j.G18);return a<<12|c},getBCHDigit:function(a){for(var c=0;0!=a;)c++,a>>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
+a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;d<a;d++)c=c.multiply(new q([1,l.gexp(d)],0));return c},getLengthInBits:function(a,c){if(1<=c&&10>c)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
+a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b<c;b++)for(var e=0;e<c;e++){for(var f=0,i=a.isDark(b,e),g=-1;1>=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5<f&&(d+=3+f-5)}for(b=0;b<c-1;b++)for(e=0;e<c-1;e++)if(f=0,a.isDark(b,e)&&f++,a.isDark(b+1,e)&&f++,a.isDark(b,e+1)&&f++,a.isDark(b+1,e+1)&&f++,0==f||4==f)d+=3;for(b=0;b<c;b++)for(e=0;e<c-6;e++)a.isDark(b,e)&&!a.isDark(b,e+1)&&a.isDark(b,e+
+2)&&a.isDark(b,e+3)&&a.isDark(b,e+4)&&!a.isDark(b,e+5)&&a.isDark(b,e+6)&&(d+=40);for(e=0;e<c;e++)for(b=0;b<c-6;b++)a.isDark(b,e)&&!a.isDark(b+1,e)&&a.isDark(b+2,e)&&a.isDark(b+3,e)&&a.isDark(b+4,e)&&!a.isDark(b+5,e)&&a.isDark(b+6,e)&&(d+=40);for(e=f=0;e<c;e++)for(b=0;b<c;b++)a.isDark(b,e)&&f++;a=Math.abs(100*f/c/c-50)/5;return d+10*a}},l={glog:function(a){if(1>a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
+LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<<m;for(m=8;256>m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d<this.getLength();d++)for(var b=0;b<a.getLength();b++)c[d+b]^=l.gexp(l.glog(this.get(d))+l.glog(a.get(b)));return new q(c,0)},mod:function(a){if(0>
+this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b<this.getLength();b++)d[b]=this.get(b);for(b=0;b<a.getLength();b++)d[b]^=l.gexp(l.glog(a.get(b))+c);return(new q(d,0)).mod(a)}};p.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],
+[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,
+116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,
+43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,
+3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,
+55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,
+45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];p.getRSBlocks=function(a,c){var d=p.getRsBlockTable(a,c);if(void 0==d)throw Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+c);for(var b=d.length/3,e=[],f=0;f<b;f++)for(var h=d[3*f+0],g=d[3*f+1],j=d[3*f+2],l=0;l<h;l++)e.push(new p(g,j));return e};p.getRsBlockTable=function(a,c){switch(c){case 1:return p.RS_BLOCK_TABLE[4*(a-1)+0];case 0:return p.RS_BLOCK_TABLE[4*(a-1)+1];case 3:return p.RS_BLOCK_TABLE[4*
+(a-1)+2];case 2:return p.RS_BLOCK_TABLE[4*(a-1)+3]}};t.prototype={get:function(a){return 1==(this.buffer[Math.floor(a/8)]>>>7-a%8&1)},put:function(a,c){for(var d=0;d<c;d++)this.putBit(1==(a>>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
+correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f<a.getModuleCount();f++)for(var i=0;i<a.getModuleCount();i++){d.fillStyle=a.isDark(f,i)?h.foreground:h.background;var g=Math.ceil((i+1)*b)-Math.floor(i*b),
+j=Math.ceil((f+1)*b)-Math.floor(f*b);d.fillRect(Math.round(i*b),Math.round(f*e),g,j)}}else{a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();c=r("<table></table>").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e<a.getModuleCount();e++){f=r("<tr></tr>").css("height",b+"px").appendTo(c);for(i=0;i<a.getModuleCount();i++)r("<td></td>").css("width",
+d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
diff --git a/simpleid/www/html/lock-open.png b/simpleid/www/html/lock-open.png
new file mode 100644
index 0000000..a471765
--- /dev/null
+++ b/simpleid/www/html/lock-open.png
Binary files differ
diff --git a/simpleid/www/html/lock.png b/simpleid/www/html/lock.png
new file mode 100644
index 0000000..2ebc4f6
--- /dev/null
+++ b/simpleid/www/html/lock.png
Binary files differ
diff --git a/simpleid/www/html/nav-toggle.png b/simpleid/www/html/nav-toggle.png
new file mode 100644
index 0000000..063079e
--- /dev/null
+++ b/simpleid/www/html/nav-toggle.png
Binary files differ
diff --git a/simpleid/www/html/normalize.css b/simpleid/www/html/normalize.css
new file mode 100644
index 0000000..c2de8df
--- /dev/null
+++ b/simpleid/www/html/normalize.css
@@ -0,0 +1,406 @@
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9.
+ * Hide the `template` element in IE, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background: transparent;
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/simpleid/www/html/openid-consent.js b/simpleid/www/html/openid-consent.js
new file mode 100644
index 0000000..52bd63c
--- /dev/null
+++ b/simpleid/www/html/openid-consent.js
@@ -0,0 +1,7 @@
+$(document).ready(function() {
+ $('.return-to-suspect').click(function() {
+ if ($(this).attr('checked') == false) return true;
+
+ return confirm(l.openid_suspect);
+ });
+});
diff --git a/simpleid/www/html/page-profile.js b/simpleid/www/html/page-profile.js
new file mode 100644
index 0000000..a64da93
--- /dev/null
+++ b/simpleid/www/html/page-profile.js
@@ -0,0 +1,21 @@
+var discovery_refresh = function() {
+ var code = '';
+
+ if ($('#discovery-openid1')[0].checked) {
+ code += $('#discovery-templates .openid1').html() + '\n';
+ if ($('#discovery-local-id')[0].checked) code += $('.openid1-local-id').html() + '\n';
+ }
+ if ($('#discovery-openid2')[0].checked) {
+ code += $('#discovery-templates .openid2').html() + '\n';
+ if ($('#discovery-local-id')[0].checked) code += $('.openid2-local-id').html() + '\n';
+ }
+
+ if (code == '') code = l.code;
+
+ $('#discovery-link-tags').html(code);
+}
+
+$(document).ready(function() {
+ $('.discovery-checkbox').click(discovery_refresh);
+ discovery_refresh();
+});
diff --git a/simpleid/www/html/simpleid.css b/simpleid/www/html/simpleid.css
new file mode 100644
index 0000000..02dc644
--- /dev/null
+++ b/simpleid/www/html/simpleid.css
@@ -0,0 +1,383 @@
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+@import url(normalize.css);
+
+/* General elements -------------------------------------------------------- */
+html {
+ font: 12px/19px Helvetica, Arial, sans-serif;
+}
+
+h1 {
+ font-weight: normal;
+ color: #666666;
+}
+
+a {
+ border-bottom: 1px solid #CCCCCC;
+ color: #1144AA;
+ text-decoration: none !important;
+ font-weight: bold;
+}
+a:visited {
+ border-color: #CCCCCC;
+ color: #114499;
+}
+a:focus, a:hover {
+ border-color: #4488EE;
+ color: #4488EE;
+}
+
+table {
+ border-collapse: collapse;
+ margin: 0 0 1em 0;
+ width: 100%;
+}
+
+td, th {
+ border-bottom: 1px solid #CCCCCC;
+ text-align: left;
+ vertical-align: top;
+ padding: 5px 5px;
+}
+th {
+ border-bottom: 0px;
+ background-color: #666666;
+ color: #FFFFFF;
+ white-space: nowrap;
+ font-weight: bold;
+}
+
+pre {
+ border:1px solid #CCCCCC;
+ padding: 5px;
+ font-family:"Bitstream Vera Sans Mono","Courier New",monospace;
+ font-size: 0.9em;
+ white-space: nowrap;
+ overflow: scroll;
+}
+
+/* Page layout ------------------------------------------------------------- */
+#header {
+ background-color: #666666;
+ color: #CCCCCC;
+ padding: 0px 10px;
+ line-height: 1;
+}
+#header-inner { margin: 0px auto; max-width: 1140px; }
+
+#header h1 {
+ font-weight: bold;
+ font-size: 14px;
+ text-transform: uppercase;
+ margin: 0.5em 0;
+ color: #CCCCCC;
+ letter-spacing: 0;
+}
+
+#header h1 a {
+ color: #FFFFFF;
+ text-decoration: none;
+ border-bottom: none;
+}
+
+#nav-toggle, #logo { float: left; padding: 5px 0px; }
+#nav-toggle {
+ padding: 5px;
+ border-right: 1px solid #FFFFFF;
+ margin-right: 0.5em;
+ cursor: pointer;
+ display: none;
+}
+#nav-toggle img { padding: 0.5em; }
+
+#nav {
+ background-color: #666666;
+ color: #CCCCCC;
+ padding: 5px 10px;
+}
+#nav-inner { margin: 0px auto; max-width: 1140px; }
+#nav ul {
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+#nav li {
+ display: inline;
+ list-style-type: none;
+}
+#nav a {
+ background-color: #888888;
+ padding: 6px 8px;
+ border-width: 0;
+ color: #FFFFFF;
+}
+#nav ul#nav-left { float: left; }
+#nav ul#nav-right { float: right; }
+
+#user {
+ float: right;
+ margin: 0.5em 0;
+ padding: 5px 0;
+}
+.logged-in-as .identity {
+ padding-left: 20px;
+ background: transparent url(user.png) no-repeat top left;
+ color: #FFFFFF;
+}
+.logged-in-as a {
+ color: #FFFFFF;
+ border-bottom-color: #FFFFFF;
+ font-weight: normal;
+}
+#logout:before { content: " · "; }
+
+#user-toggle {
+ float: right;
+ padding: 5px;
+ margin-left: 0.5em;
+ border-left: 1px solid #FFFFFF;
+ cursor: pointer;
+ display: none;
+}
+#user-toggle img { padding: 0.5em; }
+
+#footer {
+ margin: 2em 0;
+ padding: 0px 10px;
+ text-align: right;
+ font-size: 0.9em;
+ color: #999999;
+}
+#footer address { font-style: normal }
+#footer a { color: #999999; }
+
+#content {
+ margin: auto;
+ padding: 1em 10px;
+ max-width: 1140px;
+ clear: both;
+}
+
+
+/* Specific elements ------------------------------------------------------- */
+.realm, .site {
+ padding-left: 20px;
+ background: transparent url(world.png) no-repeat top left;
+}
+.app {
+ padding-left: 20px;
+ background: transparent url(application.png) no-repeat top left;
+}
+.device {
+ padding-left: 20px;
+ background: transparent url(drive.png) no-repeat top left;
+}
+.url-elide { color: #999999; }
+
+span.last-time { display: none; }
+
+.message, .login-security {
+ border: 1px solid #666666;
+ border-radius: 4px;
+ padding: 0 10px;
+ background: #DDDDDD;
+ margin-bottom: 10px;
+}
+.message p { line-height: 1; }
+
+.unsecure {
+ border: 1px solid #AAAA11;
+ background: #FFFFDD;
+}
+.unsecure p { padding-left: 20px; background: transparent url(lock-open.png) no-repeat top left; }
+.secure {
+ border: 1px solid #44AA11;
+ background: #EEFFDD;
+}
+.secure p { padding-left: 20px; background: transparent url(lock.png) no-repeat top left; }
+
+.block {
+ background-color: #EEEEEE;
+ margin-bottom: 20px;
+}
+.block-header {
+ background-color: #666666;
+ margin:0 0 5px;
+ padding: 5px 10px;
+}
+.block-header h2 {
+ font-size: 1.2em;
+ font-weight: bold;
+ color: #FFFFFF;
+ margin: 0;
+ padding: 0;
+ border-top-width: 0;
+}
+.block-header-links {
+ float: right;
+ font-size: 0.9em;
+ text-align: right;
+ white-space: nowrap;
+}
+.block-header-links a {
+ color: #FFFFFF;
+ border-bottom-color: #FFFFFF;
+ font-weight: normal;
+}
+.block-content {
+ padding: 5px 10px;
+}
+.block-content p {
+ margin: 0 0 5px 0;
+ padding: 0;
+}
+.block-content h3 {
+ font-size: 1em;
+ font-weight: bold;
+ margin: 0;
+ padding: 5px 0 0 0;
+}
+
+#discovery label { display: inline; }
+#discovery-templates { display: none; }
+
+.otp-key {
+ font-size: 18px;
+ font-family: "Bitstream Vera Sans Mono","Courier New",monospace;
+ margin: 5px;
+}
+.otp-letters { padding: 0 2px; }
+#otp-key-qr { padding: 16px 0; }
+
+/* Forms */
+.form-item { margin: 1em 0; }
+
+label {
+ display: block;
+ font-weight: bold;
+ padding: 2px 0;
+}
+label.option { font-weight: normal; }
+
+select, textarea, input[type="text"], input[type="password"], input[type="number"] {
+ display: inline-block;
+ padding: 0.5em;
+ border: 1px solid #AAAAAA;
+ border-radius: 2px;
+}
+select:focus, textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
+ border-color: #1144AA;
+}
+
+input[type="submit"], input[type="button"] {
+ padding: 0.5em 1em;
+ background-color: #EEEEEE;
+ border: 1px solid #999999;
+ border-radius: 2px;
+ zoom: 1;
+ vertical-align: middle;
+ font-weight: bold;
+}
+input[type="submit"]:active, input[type="button"]:active {
+ box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
+}
+input[type="submit"][disabled], input[type="button"][disabled] {
+ color: #999999 !important;
+ background-color: #EEEEEE !important;
+ border-color: #999999 !important;
+}
+input[type="submit"].form-default, input[type="button"].form-default {
+ color: #FFFFFF;
+ background-color: #1144AA;
+ border-color: #114499;
+}
+
+input[type="radio"], input[type="checkbox"] { margin: 0.5em 0; }
+input[type="radio"]:focus, input[type="checkbox"]:focus {
+ outline: 1px auto #1144AA;
+}
+
+/* Dialogs and dialog pages */
+.dialog-page { background: #EEEEEE; }
+.dialog-page #content {
+ padding-top: 50px;
+ max-width: 400px;
+}
+.dialog-page #content-inner {
+ padding: 20px;
+ border: 10px solid #DDDDDD;
+ background: #FFFFFF;
+}
+
+.dialog-page form { padding-right: 2px; } /* Used to adjust the padding in text boxes */
+.dialog-page .form-item { padding-right: 1em; } /* Used to adjust the padding in text boxes */
+.dialog-page select, .dialog-page textarea, .dialog-page input[type="text"], .dialog-page input[type="password"], .dialog-page input[type="number"] {
+ width: 100%;
+}
+
+/* Mobile devices ---------------------------------------------------------- */
+@media only screen and (max-width: 767px) {
+ html { font-size: 14px; }
+
+ #logo .version { display: none; }
+
+ #nav-toggle, #user-toggle { display: block; }
+ #nav-toggle.expand, #user-toggle.expand { background-color: #888888; }
+
+ #nav {
+ display: none;
+ position: absolute;
+ float: none;
+ left: 10px;
+ top: 40px;
+ padding: 0;
+ }
+ #nav.expand { display: block; }
+ #nav li { display: block; }
+ #nav a { display: block; }
+
+ #user {
+ display: none;
+ position: absolute;
+ float: none;
+ right: 10px;
+ top: 40px;
+ background-color: #888888;
+ margin: 0;
+ padding: 0 8px;
+ }
+ #user.expand { display: block; }
+ #user span {
+ display: block;
+ padding: 6px 0;
+ }
+ #logout:before { content: ""; }
+
+ .dialog-page { background: #FFFFFF; }
+ .dialog-page #content { padding-top: 10px; }
+ .dialog-page #content-inner {
+ padding: 0px;
+ border-width: 0px;
+ }
+}
+
diff --git a/simpleid/www/html/simpleid.ico b/simpleid/www/html/simpleid.ico
new file mode 100644
index 0000000..b35053e
--- /dev/null
+++ b/simpleid/www/html/simpleid.ico
Binary files differ
diff --git a/simpleid/www/html/simpleid.png b/simpleid/www/html/simpleid.png
new file mode 100644
index 0000000..25bde22
--- /dev/null
+++ b/simpleid/www/html/simpleid.png
Binary files differ
diff --git a/simpleid/www/html/template.xtpl b/simpleid/www/html/template.xtpl
new file mode 100644
index 0000000..66d1aff
--- /dev/null
+++ b/simpleid/www/html/template.xtpl
@@ -0,0 +1,419 @@
+<!-- BEGIN: main --><!DOCTYPE html>
+<html lang="en">
+ <!-- :mode=html: $Id$ -->
+ <head>
+ <title>{title} - SimpleID</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="SimpleID-Version" content="{version}" />
+ <meta name="robots" content="noindex,nofollow" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+ <link rel="shortcut icon" href="{base_path}html/simpleid.ico" type="image/x-icon" />
+
+ <!-- BEGIN: provider -->
+ <link rel="openid.server" href="{provider}" />
+ <link rel="openid2.provider" href="{provider}" />
+ <meta content="{xrds}" http-equiv="X-XRDS-Location" />
+ <!-- END: provider -->
+ <!-- BEGIN: local_id -->
+ <link rel="openid.delegate" href="{local_id}" />
+ <link rel="openid2.local_id" href="{local_id}" />
+ <!-- END: local_id -->
+
+ <link rel="stylesheet" href="{base_path}html/simpleid.css" />
+ <style type="text/css" media="screen">
+ {css}
+ </style>
+
+ <script src="{base_path}html/jquery.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var l = {
+ <!-- BEGIN: js_locale -->"{js_locale_label}": "{js_locale_text}",<!-- END: js_locale -->
+ };
+ </script>
+ <!-- BEGIN: framekiller -->
+ <script type="text/javascript">
+ $(document).ready(function() {
+ if (top !== self) top.location.replace(self.location.href);
+ });
+ </script>
+ <!-- END: framekiller -->
+ {javascript}
+ </head>
+ <body class="{page_class}">
+ <div id="header"><div id="header-inner">
+ <!-- BEGIN: nav_toggle --><div id="nav-toggle"><img src="{base_path}html/nav-toggle.png" /></div><!-- END: nav_toggle -->
+ <div id="logo">
+ <h1><a href="http://simpleid.org/">SimpleID</a> <span class="version">{version}</span></h1>
+ </div>
+ <!-- BEGIN: user -->
+ <div id="user-toggle"><img src="{base_path}html/user-toggle.png" /></div>
+ <div id="user">
+ <div class="logged-in-as">
+ <span><strong title="{identity}" class="identity">{uid}</strong></span>
+ <!-- BEGIN: logout --><span id="logout"><a href="{url}">{logout}</a></span><!-- END: logout -->
+ </div>
+ </div>
+ <!-- END: user -->
+ <div style="clear: both;"></div>
+ </div></div>
+ <!-- BEGIN: nav -->
+ <div id="nav"><div id="nav-inner">
+ <ul id="nav-left">
+ <li><a href="{nav_base}">{nav_dashboard_label}</a></li>
+ <li><a href="{nav_base}my/profile">{nav_profile_label}</a></li>
+ <li><a href="{nav_base}my/sites">{nav_sites_label}</a></li>
+ </ul>
+ <div style="clear: both"></div>
+ </div></div>
+ <!-- END: nav -->
+ <div id="content"><div id="content-inner">
+ <h1>{title}</h1>
+
+ <!-- BEGIN: message -->
+ <div class="message">
+ <p>{message}</p>
+ </div>
+ <!-- END: message -->
+
+ <!-- BEGIN: login -->
+ <!-- BEGIN: login_security -->
+ <div class="login-security {security_class}">
+ <p>{security_message}</p>
+ </div>
+ <!-- END: login_security -->
+
+ <form action="{base_path}index.php" method="post" enctype="application/x-www-form-urlencoded" id="login-form">
+ <input type="hidden" name="q" value="login"/><input name="destination" type="hidden" value="{destination}"/>
+ <input type="hidden" name="mode" value="{mode}"/><input type="hidden" name="nonce" value="{nonce}" />
+
+ <!-- BEGIN: credentials -->
+ <input type="hidden" name="digest" id="edit-digest" value="" />
+
+ <div class="form-item">
+ <label for="edit-name">{name_label}</label>
+ <!-- BEGIN: input_uid --><input type="text" maxlength="60" name="name" id="edit-name" value="" autocapitalize="off" autocorrect="off" class="form-text required" {security_disabled} /><!-- END: input_uid -->
+ <!-- BEGIN: fixed_uid --><input type="hidden" name="name" value="{uid}"/><input type="hidden" name="fixed_uid" value="1"/><div id="edit-name">{uid}</div><!-- END: fixed_uid -->
+ </div>
+ <div class="form-item">
+ <label for="edit-pass">{pass_label}</label>
+ <input type="password" name="pass" id="edit-pass" size="60" class="form-text required" {security_disabled} />
+ </div>
+ <div class="form-item">
+ <label class="option">
+ <input type="checkbox" name="autologin" value="1" />
+ {autologin_label}
+ </label>
+ </div>
+ <!-- END: credentials -->
+
+ <!-- BEGIN: otp -->
+ <input type="hidden" name="autologin" value="{autologin}" />
+
+ <p>{otp_instructions_label}</p>
+
+ <p>{otp_recovery_label}</p>
+
+ <div class="form-item">
+ <label for="edit-otp">{otp_label}</label>
+ <input type="number" maxlength="6" name="otp" id="edit-otp" size="10" value="" autocapitalize="off" autocorrect="off" class="form-text required" />
+ </div>
+ <!--div class="form-item">
+ <label class="option">
+ <input type="checkbox" name="autoverify" value="1" />
+ {autoverify_label}
+ </label>
+ </div-->
+ <!-- END: otp -->
+
+ <input type="submit" name="op" id="edit-submit" value="{submit_button}" class="form-default" {security_disabled} />
+ <!-- BEGIN: state -->
+ <input type="submit" name="op" id="edit-cancel" value="{cancel_button}" />
+ <input type="hidden" name="s" value="{state}"/>
+ <!-- END: state -->
+ </form>
+ <!-- END: login -->
+
+ <!-- BEGIN: openid_consent -->
+ <form action="{base_path}index.php" method="post" enctype="application/x-www-form-urlencoded" id="rp-form">
+ <input type="hidden" name="q" value="openid/consent" />
+ <input type="hidden" name="s" value="{state}" />
+ <input name="tk" type="hidden" value="{token}"/>
+
+ <!-- BEGIN: icon -->
+ <div class="icon">
+ <img src="{icon_url}" alt="" />
+ </div>
+ <!-- END: icon -->
+
+ <!-- BEGIN: setup -->
+ <input type="hidden" name="openid.realm" value="{realm}" />
+
+ <p>{realm_label}</p>
+
+ <!-- BEGIN: suspect -->
+ <div class="message unsecure">
+ <p>{suspect_label}</p>
+ </div>
+ <!-- END: suspect -->
+
+ <div class="form-item">
+ <label class="option">
+ <input class="{realm_class}" type="checkbox" name="autorelease" {auto_release} value="1" />
+ {auto_release_label}
+ </label>
+ </div>
+
+ {extensions}
+
+ <input type="submit" name="op" id="edit-submit" value="{ok_button}" class="form-default" />
+ <input type="submit" name="op" id="edit-cancel" value="{cancel_button}" />
+ <!-- END: setup -->
+
+ <!-- BEGIN: cancel -->
+ <input type="hidden" name="openid.return_to" value="{return_to}" />
+
+ <p>{unable_label}</p>
+
+ <p>{identity_not_matching_label}</p>
+
+ <p>{switch_user_label}</p>
+
+ <input type="submit" name="op" id="edit-cancel" value="{cancel_button}" class="form-default" />
+ <!-- END: cancel -->
+ </form>
+ <!-- END: openid_consent -->
+
+ <!-- BEGIN: blocks -->
+ {blocks}
+ <!-- END: blocks -->
+
+ <!-- BEGIN: sites -->
+ <form action="{base_path}index.php" method="post" enctype="application/x-www-form-urlencoded" >
+ <input name="q" type="hidden" value="my/sites" />
+ <input name="tk" type="hidden" value="{token}"/>
+ <input name="update-all" type="hidden" value="1"/>
+ <table id="sites">
+ <thead>
+ <tr>
+ <th>{realm_label}</th>
+ <th>{last_time_label}</th>
+ <th>{auto_release_label}</th>
+ <th>{remove_label}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- BEGIN:realm -->
+ <tr>
+ <td><span class="realm">{realm_name}</span></td>
+ <td><span class="last-time">{last_time}</span> {last_time_formatted}</td>
+ <td><input class="{realm_class}" type="checkbox" value="1" name="autorelease[{realm}]" {auto_release}/></td>
+ <td><input type="checkbox" value="1" name="remove[{realm}]"/></td>
+ </tr>
+ <!-- END: realm -->
+ </tbody>
+ </table>
+ <input type="submit" name="op" id="edit-submit" value="{submit_button}" class="form-default" {disabled} />
+ </form>
+ <!-- END: sites -->
+
+ <!-- BEGIN: otp -->
+ <p>{about_otp}</p>
+
+ <p>{otp_warning}</p>
+
+ <p>{setup_otp}</p>
+
+ <ol>
+ <li>{download_app}</li>
+ <li>{add_account}
+ <div class="otp-key"><span class="otp-letters">{secret1}</span><span class="otp-letters">{secret5}</span><span class="otp-letters">{secret9}</span><span class="otp-letters">{secret13}</span></div>
+ <div id="otp-key-qr"></div>
+ </li>
+ <li>{verify_code}</li>
+ </ol>
+
+ <script src="{base_path}html/jquery.qrcode.js" type="text/javascript"></script><script type="text/javascript">$('#otp-key-qr').qrcode("{qr}");</script>
+
+ <form action="{base_path}index.php" method="post" enctype="application/x-www-form-urlencoded">
+ <input type="hidden" name="q" value="otp"/><input type="hidden" name="tk" value="{token}"/>
+
+ <div class="form-item">
+ <label for="edit-otp">{otp_label}</label>
+ <input type="number" maxlength="6" name="otp" id="edit-otp" size="10" value="" autocapitalize="off" autocorrect="off" class="form-text required" />
+ </div>
+ <input type="submit" name="op" id="edit-submit" value="{submit_button}" class="form-default" />
+ </form>
+
+ <!-- END: otp -->
+
+ <!-- BEGIN: upgrade_access_denied -->
+ <p>{login_required}</p>
+
+ <p>{edit_upgrade_php}</p>
+
+ <ol>
+ <li>{edit_upgrade_php1}</li>
+ <li>{edit_upgrade_php2}</li>
+ <li>{edit_upgrade_php3}</li>
+ <li>{edit_upgrade_php4}</li>
+ </ol>
+
+ <p>{simpleid_docs}</p>
+ <!-- END: upgrade_access_denied -->
+
+ <!-- BEGIN: upgrade_info -->
+ <p>{intro}</p>
+
+ <p>{simpleid_docs}</p>
+
+ <ol>
+ <li>{step1}</li>
+ <li>{step2}</li>
+ </ol>
+
+ <p>{click_continue}</p>
+
+ <form method="post" action="{base_path}upgrade.php">
+ <input type="hidden" name="q" value="upgrade-selection" />
+ <input type="hidden" name="tk" value="{token}" />
+ <input type="submit" value="{continue_button}" />
+ </form>
+ <!-- END: upgrade_info -->
+
+ <!-- BEGIN: upgrade_selection -->
+ <p>{version_detected}</p>
+
+ <p>{original_version_label}: <strong class="upgrade-version original-version">{original_version}</strong></p>
+
+ <p>{this_version_label}: <strong class="upgrade-version this-version">{this_version}</strong></p>
+
+ <!-- BEGIN: selection_complete -->
+ <p>{script_complete}</p>
+ <!-- BEGIN: upgrade_access_check -->
+ <div class="message unsecure">
+ <p>{edit_upgrade_php}</p>
+ </div>
+ <!-- END: upgrade_access_check -->
+ <!-- END: selection_complete -->
+
+ <!-- BEGIN: selection_continue -->
+ <p>{click_continue}</p>
+
+ <form method="post" action="{base_path}upgrade.php">
+ <input type="hidden" name="q" value="upgrade-apply" />
+ <input type="hidden" name="tk" value="{token}" />
+ <input type="hidden" name="handle" value="{handle}" />
+ <input type="submit" value="{continue_button}" />
+ </form>
+ <!-- END: selection_continue -->
+
+ <!-- END: upgrade_selection -->
+
+ <!-- BEGIN: upgrade_results -->
+ <p>{upgrade_complete}</p>
+ <!-- BEGIN: upgrade_access_check -->
+ <div class="message unsecure">
+ <p>{edit_upgrade_php}</p>
+ </div>
+ <!-- END: upgrade_access_check -->
+
+ <div class="upgrade-results">
+ {results}
+ </div>
+ <!-- END: upgrade_results -->
+ </div></div>
+
+ <div id="footer">
+ <address>
+ SimpleID {version} © Kelvin Mo ·
+ <a href="http://simpleid.org/docs/1/">{footer_doc}</a> ·
+ <a href="http://github.com/simpleid/simpleid/">{footer_support}</a>
+ </address>
+ </div>
+
+ <script type="text/javascript">
+ (function() {
+ var nav, nav_toggle, user, user_toggle;
+
+ nav = document.getElementById('nav');
+ nav_toggle = document.getElementById('nav-toggle');
+ user = document.getElementById('user');
+ user_toggle = document.getElementById('user-toggle');
+
+ if (nav) {
+ nav_toggle.onclick = function() {
+ if (nav.className.indexOf('expand') !== -1) {
+ nav.className = nav.className.replace(' expand', '');
+ nav_toggle.className = nav_toggle.className.replace(' expand', '');
+ } else {
+ nav.className += ' expand';
+ nav_toggle.className += ' expand';
+ }
+ }
+ }
+
+ if (user) {
+ user_toggle.onclick = function() {
+ if (user.className.indexOf('expand') !== -1) {
+ user.className = user.className.replace(' expand', '');
+ user_toggle.className = user_toggle.className.replace(' expand', '');
+ } else {
+ user.className += ' expand';
+ user_toggle.className += ' expand';
+ }
+ }
+ }
+ })();
+ </script>
+ </body>
+</html>
+<!-- END: main -->
+
+<!-- BEGIN: xrds --><?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns="xri://$xrd*($v*2.0)" xmlns:xrds="xri://$xrds" xmlns:simple="http://xrds-simple.net/core/1.0">
+ <XRD version="2.0">
+ <!-- BEGIN: user_xrds -->
+ <Service priority="10">
+ <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ <URI>{simpleid_base_url}</URI>
+ <!-- BEGIN: local_id2 -->
+ <LocalID>{local_id}</LocalID>
+ <!-- END: local_id2 -->
+ </Service>
+ <Service priority="20" xmlns:openid="http://openid.net/xmlns/1.0">
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>{simpleid_base_url}</URI>
+ <!-- BEGIN: local_id -->
+ <openid:Delegate>{local_id}</openid:Delegate>
+ <!-- END: local_id -->
+ </Service>
+ <!-- END: user_xrds -->
+ <!-- BEGIN: op_xrds -->
+ <Service>
+ <Type>http://specs.openid.net/auth/2.0/server</Type>
+ <!-- Keep old domain -->
+ <Type>http://simpleid.koinic.net/type/version#{version}</Type>
+ <!-- BEGIN: type --><Type>{uri}</Type><!-- END: type -->
+ <URI>{simpleid_base_url}</URI>
+ </Service>
+ <!-- END: op_xrds -->
+ </XRD>
+</xrds:XRDS>
+<!-- END: xrds -->
+
+<!-- BEGIN: xrd --><?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+ {signature}
+ <!-- BEGIN: user_xrd -->
+ <Subject>{acct_uri}</Subject>
+ <Alias>{simpleid_user_uri}</Alias>
+ <!-- BEGIN: local_id -->
+ <Alias>{local_id}</Alias>
+ <!-- END: local_id -->
+ <Link rel="http://specs.openid.net/auth/2.0/provider" href="{simpleid_base_url}" />
+ <!-- END: user_xrd -->
+ <!-- BEGIN: xrd_link -->
+ <Link rel="{rel}" href="{href}" />
+ <!-- END: xrd_link -->
+</XRD>
+<!-- END: xrd -->
diff --git a/simpleid/www/html/upgrade.css b/simpleid/www/html/upgrade.css
new file mode 100644
index 0000000..780daee
--- /dev/null
+++ b/simpleid/www/html/upgrade.css
@@ -0,0 +1,14 @@
+.upgrade-version {
+ font-weight: bold;
+ font-size: 1.5em;
+}
+
+.original-version { color: #666666; }
+.this-version { color: #4A80ED; }
+
+.upgrade-results {
+ overflow: auto;
+ height: 200px;
+ border: 1px solid #666666;
+ background-color: #EEEEEE;
+}
diff --git a/simpleid/www/html/user-login.js b/simpleid/www/html/user-login.js
new file mode 100644
index 0000000..bbcc227
--- /dev/null
+++ b/simpleid/www/html/user-login.js
@@ -0,0 +1,7 @@
+$(document).ready(function() {
+ if (!$('.login-security').is('.allow-autocomplete')) $('#edit-pass').attr('autocomplete', 'off');
+
+ if ($('#edit-name').is('.form-text')) $('#edit-name').focus();
+ if ($('#edit-otp').is('.form-text')) $('#edit-otp').focus();
+});
+
diff --git a/simpleid/www/html/user-toggle.png b/simpleid/www/html/user-toggle.png
new file mode 100644
index 0000000..a68304c
--- /dev/null
+++ b/simpleid/www/html/user-toggle.png
Binary files differ
diff --git a/simpleid/www/html/user.png b/simpleid/www/html/user.png
new file mode 100644
index 0000000..79f35cc
--- /dev/null
+++ b/simpleid/www/html/user.png
Binary files differ
diff --git a/simpleid/www/html/world.png b/simpleid/www/html/world.png
new file mode 100644
index 0000000..68f21d3
--- /dev/null
+++ b/simpleid/www/html/world.png
Binary files differ
diff --git a/simpleid/www/http.inc.php b/simpleid/www/http.inc.php
new file mode 100644
index 0000000..37c1345
--- /dev/null
+++ b/simpleid/www/http.inc.php
@@ -0,0 +1,359 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-9
+ *
+ * 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$
+ */
+
+/**
+ * Functions for making and processing HTTP requests.
+ *
+ * @package simpleid
+ * @since 0.7
+ * @filesource
+ */
+
+/**
+ * The user agent to use during HTTP requests.
+ */
+define('SIMPLEHTTP_USER_AGENT', 'SimpleHTTP/' . substr('$Rev$', 6, -2));
+
+/**
+ * Performs an HTTP request.
+ *
+ * Communication with the web server is conducted using libcurl where possible.
+ * Where libcurl does not exist, then sockets will be used.
+ *
+ * Note that the request must be properly prepared before passing onto this function.
+ * For example, for POST requests, the Content-Type and Content-Length headers must be
+ * included in $headers.
+ *
+ * @param string $url the URL
+ * @param array $headers HTTP headers containing name => value pairs
+ * @param string $body the request body
+ * @param string $method the HTTP request method
+ * @param int $retry the maximum number of redirects allowed
+ * @return array containing keys 'error-code' (for communication errors), 'error'
+ * (for communication errors), 'data' (content returned), 'code' (the HTTP status code), 'http-error'
+ * (if the HTTP status code is not 200 or 304), 'protocol' (the HTTP protocol in the response),
+ * 'headers' (an array of return headers in lowercase),
+ * 'content-type' (the HTTP content-type returned)
+ */
+function http_make_request($url, $headers = array(), $body = NULL, $method = 'GET', $retry = 3) {
+ // If CURL is available, we use it
+ if (extension_loaded('curl')) {
+ $response = _http_make_request_curl($url, $headers, $body, $method, $retry);
+ } else {
+ $response = _http_make_request_fsock($url, $headers, $body, $method, $retry);
+ }
+
+ if (!isset($response['error-code'])) {
+ $valid_codes = array(
+ 100, 101,
+ 200, 201, 202, 203, 204, 205, 206,
+ 300, 301, 302, 303, 304, 305, 307,
+ 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417,
+ 500, 501, 502, 503, 504, 505
+ );
+
+ // RFC 2616 states that all unknown HTTP codes must be treated the same as the
+ // base code in their class.
+ if (!in_array($response['code'], $valid_codes)) {
+ $response['code'] = floor($response['code'] / 100) * 100;
+ }
+
+ if (($response['code'] != 200) && ($response['code'] != 304)) {
+ $response['http-error'] = $response['code'];
+ }
+
+ }
+
+ return $response;
+}
+
+/**
+ * Returns the protocols currently supported for making remote requests.
+ *
+ * If libcurl is used, this function returns a list of protocols supported by the
+ * included build of the library. If libcurl is not used, then HTTP is the
+ * only protocol supported.
+ *
+ * @return array an array of protocols
+ */
+function http_protocols() {
+ if (extension_loaded('curl')) {
+ $curl_version = curl_version();
+ return $curl_version['protocols'];
+ } else {
+ return array('http');
+ }
+}
+
+/**
+ * Performs an HTTP request using libcurl.
+ *
+ * @param string $url the URL
+ * @param array $headers HTTP headers containing name => value pairs
+ * @param string $body the request body
+ * @param string $method the HTTP request method
+ * @param int $retry the maximum number of redirects allowed
+ * @return array containing keys 'error-code' (for communication errors), 'error'
+ * (for communication errors), 'data' (content returned), 'code' (the HTTP status code), 'http-error'
+ * (if the HTTP status code is not 200 or 304), 'headers' (an array of return headers),
+ * 'content-type' (the HTTP content-type returned)
+ */
+function _http_make_request_curl($url, $headers = array(), $body = NULL, $method = 'GET', $retry = 3) {
+ // CURLOPT_FOLLOWLOCATION only works when safe mode is off or when open_basedir is set
+ // In these instances we will need to follow redirects manually
+ $manual_redirect = ((@ini_get('safe_mode') === 1) // safe mode
+ || (strtolower(@ini_get('safe_mode')) == 'on') // safe mode
+ || (@ini_get('open_basedir') != false)); // open_basedir
+
+ $version = curl_version();
+
+ $curl = curl_init($url);
+
+ if (version_compare($version['version'], '7.10.5', '>=')) {
+ curl_setopt($curl, CURLOPT_ENCODING, '');
+ }
+
+ if (!$manual_redirect) curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
+ curl_setopt($curl, CURLOPT_MAXREDIRS, $retry);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, array(implode("\n", $headers) . "\n"));
+ curl_setopt($curl, CURLOPT_USERAGENT, SIMPLEHTTP_USER_AGENT);
+
+ curl_setopt($curl, CURLOPT_TIMEOUT, 20);
+ curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
+
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_HEADER, true);
+
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+
+ if ($body != NULL) curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
+
+ $response = curl_exec($curl);
+
+ if (($response === FALSE) && ((curl_errno($curl) == 23) || (curl_errno($curl) == 61))) {
+ curl_setopt($curl, CURLOPT_ENCODING, 'none');
+ $response = curl_exec($curl);
+ }
+
+ if ($response === FALSE) {
+ $result = array();
+ $result['error-code'] = curl_errno($curl);
+ $result['error'] = curl_error($curl);
+ } else {
+ $result['code'] = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ $result['url'] = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
+ $result['content-type'] = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
+
+ // Parse response.
+ $result['raw'] = $response;
+
+ $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+ $result['data'] = substr($response, $header_size);
+
+ $response_headers = substr($response, 0, $header_size - 4);
+
+ // In case where redirect occurs, we want the last set of headers
+ $header_blocks = explode("\r\n\r\n", $response_headers);
+ $header_block = array_pop($header_blocks);
+
+ $result = array_merge($result, _http_parse_headers($header_block, TRUE));
+
+ // If we are in safe mode, we need to process redirects manually
+ if ($manual_redirect && (($result['code'] == 301) || ($result['code'] == 302) || ($result['code'] == 307))) {
+ if ($retry == 0) {
+ // Too many times, return an error
+ $result['error-code'] = 47;
+ $result['error'] = 'Too many redirects';
+ } else {
+ curl_close($curl);
+ return _http_make_request_curl($result['headers']['location'], $headers, $body, $method, $retry - 1);
+ }
+ }
+ }
+
+ curl_close($curl);
+
+ return $result;
+}
+
+/**
+ * Performs an HTTP request using sockets.
+ *
+ * @param string $url the URL
+ * @param array $headers HTTP headers containing name => value pairs
+ * @param string $body the request body
+ * @param string $method the HTTP request method
+ * @param int $retry the maximum number of redirects allowed
+ * @return array containing keys 'error-code' (for communication errors), 'error'
+ * (for communication errors), 'data' (content returned), 'code' (the HTTP status code), 'http-error'
+ * (if the HTTP status code is not 200 or 304), 'headers' (an array of return headers),
+ * 'content-type' (the HTTP content-type returned)
+ */
+function _http_make_request_fsock($url, $headers = array(), $body = NULL, $method = 'GET', $retry = 3) {
+ $result = array();
+
+ $parts = parse_url($url);
+
+ if (!isset($parts)) {
+ $result['error-code'] = 3;
+ $result['error'] = 'URL not properly formatted';
+ return $result;
+ }
+
+ if ($parts['scheme'] == 'http') {
+ $port = isset($parts['port']) ? $parts['port'] : 80;
+ $host = $parts['host'];
+ } elseif ($parts['scheme'] == 'https') {
+ $port = isset($parts['port']) ? $parts['port'] : 443;
+ $host = 'ssl://' . $parts['host'];
+ } else {
+ $result['error-code'] = 1;
+ $result['error'] = 'Unsupported protocol';
+ }
+
+ $fp = @fsockopen($host, $port, $errno, $errstr, 15);
+
+ if (!$fp) {
+ $result['error-code'] = 7;
+ $result['error'] = "Cannot connect: Error $errno:" . trim($errstr);
+ return $result;
+ }
+
+ if (isset($parts['path'])) {
+ $path = $url_parts['path'];
+ if (isset($parts['query'])) $path .= '?' . $url_parts['query'];
+ } else {
+ $path = '/';
+ }
+
+ $headers = array_merge(
+ array(
+ 'Host' => $parts['host'],
+ 'User-Agent' => SIMPLEHTTP_USER_AGENT,
+ 'Connection' => 'close'
+ ),
+ $headers
+ );
+
+ if (isset($parts['user']) && isset($parts['pass'])) {
+ $headers['Authorization'] = 'Basic '. base64_encode($parts['user'] . (!empty($parts['pass']) ? ":". $parts['pass'] : ''));
+ }
+
+ $request = $method . ' '. $path ." HTTP/1.0\r\n";
+
+ $keys = array_keys($headers);
+ for ($i = 0; $i < count($keys); $i++) {
+ $request .= $keys[$i] . ': ' . $headers[$keys[$i]] . "\r\n";
+ }
+
+ // End of headers - separator
+ $request .= "\r\n";
+
+ if ($body != NULL) $request .= $body;
+
+ fwrite($fp, $request);
+
+ // Fetch response.
+ $response = '';
+ while (!feof($fp) && $chunk = fread($fp, 1024)) {
+ $response .= $chunk;
+ }
+ fclose($fp);
+
+ // Parse response.
+ list($header_block, $result['data']) = explode("\r\n\r\n", $response, 2);
+
+ $result = array_merge($result, _http_parse_headers($header_block, FALSE));
+
+ // Process redirects
+ if (($result['code'] == 301) || ($result['code'] == 302) || ($result['code'] == 307)) {
+ if ($retry == 0) {
+ // Too many times, return an error
+ $result['error-code'] = 47;
+ $result['error'] = 'Too many redirects';
+ } else {
+ $result = _http_make_request_fsock($result['headers']['location'], $headers, $body, $method, $retry - 1);
+ }
+ }
+
+ $result['url'] = $url;
+ return $result;
+}
+
+/**
+ * Parses HTTP response headers.
+ *
+ * @param string $header_block the unparsed header block
+ * @param bool $curl if true, use simplified parsing as libcurl already parses
+ * the headers
+ * @return an array containing the following keys: 'protocol' (the HTTP protocol in the response),
+ * 'headers' (an array of return headers in lowercase). If $curl is false, additional
+ * parsing is done for 'code' and 'content-type'
+ */
+function _http_parse_headers($header_block, $curl) {
+ $headers = array();
+ $result = array();
+
+ // Split the status line from the rest of the message header
+ list($status, $header_block) = preg_split("/\r\n|\n|\r/", $header_block, 2);
+
+ // RFC 2616, section 4.2: Header fields can be extended over multiple lines
+ // by preceding each extra line with at least one space or tab. So we need
+ // to join them...
+ $header_block = preg_replace('/(\r\n|\n|\r)( |\t)+/', '', $header_block);
+
+ // Then split them to get the fields
+ $fields = preg_split("/\r\n|\n|\r/", $header_block);
+
+ // Parse the status line
+ list($protocol, $code, $reason) = explode(' ', trim($status), 3);
+
+ $result['protocol'] = $protocol;
+ if (!$curl) $result['code'] = $code;
+
+ // Parse headers.
+ while ($field = trim(array_shift($fields))) {
+ list($header, $value) = explode(':', $field, 2);
+
+ // Headers are case insensitive
+ $header = strtolower($header);
+
+ if (isset($headers[$header])) {
+ // RFC 2616, section 4.2: Multiple headers with the same field
+ // name is the same as a concatenating all the headers in a single
+ // header, separated by commas.
+ $headers[$header] .= ','. trim($value);
+ } else {
+ $headers[$header] = trim($value);
+ }
+
+ if (!$curl && (strtolower($header) == 'content-type')) $result['content-type'] = $value;
+ }
+
+ $result['headers'] = $headers;
+ return $result;
+}
+?>
diff --git a/simpleid/www/index.php b/simpleid/www/index.php
new file mode 100644
index 0000000..756e725
--- /dev/null
+++ b/simpleid/www/index.php
@@ -0,0 +1,1206 @@
+<?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$
+ */
+
+/**
+ * Main SimpleID file.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+include_once "version.inc.php";
+include_once "locale.inc.php";
+
+// Check if the configuration file has been defined
+if (file_exists('conf/config.php')) {
+ include_once 'conf/config.php';
+} elseif (file_exists('config.php')) {
+ include_once 'config.php';
+} else {
+ die(t('No configuration file found. See the <a href="!url">manual</a> for instructions on how to set up a configuration file.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+}
+
+include_once "config.default.php";
+include_once "log.inc.php";
+include_once "common.inc.php";
+include_once "simpleweb.inc.php";
+include_once "openid.inc.php";
+include_once "discovery.inc.php";
+include_once "user.inc.php";
+include_once "cache.inc.php";
+include_once SIMPLEID_STORE . ".store.php";
+include_once "page.inc.php";
+include_once "lib/xtemplate.class.php";
+
+define('CACHE_DIR', SIMPLEID_CACHE_DIR);
+
+/**
+ */
+define('CHECKID_OK', 127);
+define('CHECKID_RETURN_TO_SUSPECT', 3);
+define('CHECKID_APPROVAL_REQUIRED', 2);
+define('CHECKID_LOGIN_REQUIRED', -1);
+define('CHECKID_IDENTITIES_NOT_MATCHING', -2);
+define('CHECKID_IDENTITY_NOT_EXIST', -3);
+define('CHECKID_PROTOCOL_ERROR', -127);
+
+define('ASSOCIATION_PRIVATE', 2);
+define('ASSOCIATION_SHARED', 1);
+
+
+/**
+ * This variable holds the version of the OpenID specification associated with
+ * the current OpenID request. This can be either {@link OPENID_VERSION_1_1}
+ * or {@link OPENID_VERSION_2}.
+ *
+ * @global float $version
+ */
+$version = OPENID_VERSION_1_1;
+
+/**
+ * This variable holds an instance of the XTemplate engine.
+ *
+ * @global object $xtpl
+ */
+$xtpl = NULL;
+
+/**
+ * This variable holds the combined $_GET and $_POST superglobal arrays.
+ * This is then passed through {@link fix_http_request()}.
+ *
+ * @global array $GETPOST
+ */
+$GETPOST = array();
+
+simpleid_start();
+
+/**
+ * Entry point for SimpleID.
+ *
+ * @see user_init()
+ */
+function simpleid_start() {
+ global $xtpl, $GETPOST;
+
+ locale_init(SIMPLEID_LOCALE);
+
+ $xtpl = new XTemplate('html/template.xtpl');
+ $xtpl->assign('version', SIMPLEID_VERSION);
+ $xtpl->assign('base_path', get_base_path());
+ $xtpl->assign('footer_doc', t('Documentation'));
+ $xtpl->assign('footer_support', t('Support'));
+
+ if (!is_dir(SIMPLEID_IDENTITIES_DIR)) {
+ log_fatal('Identities directory not found.');
+ indirect_fatal_error(t('Identities directory not found. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/documentation/getting-started')));
+ }
+
+ if (!is_dir(SIMPLEID_CACHE_DIR) || !is_writeable(SIMPLEID_CACHE_DIR)) {
+ log_fatal('Cache directory not found or not writeable.');
+ indirect_fatal_error(t('Cache directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ }
+
+
+ if (!is_dir(SIMPLEID_STORE_DIR) || !is_writeable(SIMPLEID_STORE_DIR)) {
+ log_fatal('Store directory not found or not writeable.');
+ indirect_fatal_error(t('Store directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ }
+
+ if ((@ini_get('register_globals') === 1) || (@ini_get('register_globals') === '1') || (strtolower(@ini_get('register_globals')) == 'on')) {
+ log_fatal('register_globals is enabled in PHP configuration.');
+ indirect_fatal_error(t('register_globals is enabled in PHP configuration, which is not supported by SimpleID. See the <a href="!url">manual</a> for further information.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+
+ if (!bignum_loaded()) {
+ log_fatal('gmp/bcmath PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'gmp/bcmath', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('preg_match')) {
+ log_fatal('pcre PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'pcre', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('session_start')) {
+ log_fatal('session PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'session', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('xml_parser_create_ns')) {
+ log_fatal('xml PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'xml', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('hash')) {
+ log_fatal('hash PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'hash', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (is_numeric(@ini_get('suhosin.get.max_value_length')) && (@ini_get('suhosin.get.max_value_length') < 1024)) {
+ log_fatal('suhosin.get.max_value_length < 1024');
+ indirect_fatal_error(t('suhosin.get.max_value_length is less than 1024, which will lead to problems. See the <a href="!url">manual</a> for further information on system requirements.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+
+ fix_http_request();
+ $GETPOST = array_merge($_GET, $_POST);
+ openid_parse_request($GETPOST);
+
+ $q = (isset($GETPOST['q'])) ? $GETPOST['q'] : '';
+
+ $uaid = get_user_agent_id();
+ extension_init();
+ user_init($q);
+ log_info('Session opened for "' . $q . '" from ' . $uaid . ' [' . $_SERVER['REMOTE_ADDR'] . ', ' . gethostbyaddr($_SERVER['REMOTE_ADDR']) . ']');
+
+ // Clean stale assocations
+ cache_expire(array(
+ 'association' => SIMPLEID_ASSOC_EXPIRES_IN,
+ 'stateless' => 300)
+ );
+
+ simpleid_route($q);
+}
+
+/**
+ * Dispatches to the correct SimpleID function based on the request path. The
+ * request path usually comes from the q parameter in the query string (which may
+ * be inserted by mod_rewrite), but can come from other functions as well.
+ *
+ * @param string $q the request path
+ */
+function simpleid_route($q) {
+ $routes = array(
+ 'continue' => 'simpleid_continue',
+ 'login' => 'user_login',
+ 'logout' => 'user_logout',
+ 'my/dashboard' => 'page_dashboard',
+ 'my/sites' => 'page_sites',
+ 'my/profile' => 'page_profile',
+ 'otp' => 'user_otp_page',
+ 'openid/consent' => 'simpleid_openid_consent',
+ 'ppid/(.*)' => 'user_ppid_page',
+ 'user' => 'user_public_page',
+ 'user/(.+)' => 'user_public_page',
+ 'xrds/(.*)' => 'user_xrds',
+ 'xrds' => 'simpleid_xrds',
+ );
+ $routes = array_merge($routes, extension_invoke_all('routes'), array('.*' => 'simpleid_index'));
+
+ simpleweb_run($routes, $q);
+}
+
+/**
+ * The default route, called when the q parameter is missing or is invalid.
+ *
+ * This function performs the following:
+ *
+ * - If openid.mode is present, then the request is an OpenID request. This
+ * is passed to {@link simpleid_process_openid()}
+ * - If the Accept HTTP header contains the expression application/xrds+xml, then
+ * the request is a YADIS discovery request for SimpleID as an OpenID provider. Thi
+ * is passed to {@link simpleid_xrds()}
+ * - Otherwise, the dashboard or the login page is displayed to the user as
+ * appropriate
+ *
+ */
+function simpleid_index() {
+ global $GETPOST;
+
+ log_debug('simpleid_index');
+
+ $content_type = negotiate_content_type(array('text/html', 'application/xml', 'application/xhtml+xml', 'application/xrds+xml'));
+
+ header('Vary: Accept');
+ if (isset($GETPOST['openid.mode'])) {
+ simpleid_process_openid($GETPOST);
+ return;
+ } elseif ($content_type == 'application/xrds+xml') {
+ simpleid_xrds();
+ } else {
+ // Point to SimpleID's XRDS document
+ header('X-XRDS-Location: ' . simpleid_url('xrds'));
+ page_dashboard();
+ }
+}
+
+
+/**
+ * Process an OpenID request.
+ *
+ * This function determines the version of the OpenID specification that is
+ * relevant to this request, checks openid.mode and passes the
+ * request on to the function required to process the request.
+ *
+ * The OpenID request expressed as an array contain key-value pairs corresponding
+ * to the HTTP request. This is usually contained in the <code>$_REQUEST</code>
+ * variable.
+ *
+ * @param array $request the OpenID request
+ */
+function simpleid_process_openid($request) {
+ global $version;
+
+ $version = openid_get_version($request);
+
+ switch ($request['openid.mode']) {
+ case 'associate':
+ simpleid_associate($request);
+ return;
+ case 'checkid_immediate':
+ case 'checkid_setup':
+ check_https('redirect', true, simpleid_url('continue', 's=' . rawurlencode(pickle($request)), false, 'https'));
+
+ return simpleid_checkid($request);
+ case 'check_authentication':
+ simpleid_check_authentication($request);
+ break;
+ default:
+ if (isset($request['openid.return_to'])) {
+ // Indirect communication - send error via indirect communication.
+ header_response_code('400 Bad Request');
+ set_message(t('Invalid OpenID message.'));
+ page_dashboard();
+ } else {
+ // Direct communication
+ openid_direct_error('Invalid OpenID message.');
+ }
+ }
+}
+
+/**
+ * Processes an association request from a relying party.
+ *
+ * An association request has an openid.mode value of
+ * associate. This function checks whether the association request
+ * is valid, and if so, creates an association and sends the response to
+ * the relying party.
+ *
+ * @see _simpleid_create_association()
+ * @param array $request the OpenID request
+ * @link http://openid.net/specs/openid-authentication-1_1.html#mode_associate, http://openid.net/specs/openid-authentication-2_0.html#associations
+ *
+ */
+function simpleid_associate($request) {
+ global $version;
+
+ log_info('OpenID association request: ' . log_array($request));
+
+ $assoc_types = openid_association_types();
+ $session_types = openid_session_types(is_https(), $version);
+
+ // Common Request Parameters [8.1.1]
+ if (($version == OPENID_VERSION_1_1) && !isset($request['openid.session_type'])) $request['openid.session_type'] = '';
+ $assoc_type = $request['openid.assoc_type'];
+ $session_type = $request['openid.session_type'];
+
+ // Diffie-Hellman Request Parameters [8.1.2]
+ $dh_modulus = (isset($request['openid.dh_modulus'])) ? $request['openid.dh_modulus'] : NULL;
+ $dh_gen = (isset($request['openid.dh_gen'])) ? $request['openid.dh_gen'] : NULL;
+ $dh_consumer_public = $request['openid.dh_consumer_public'];
+
+ if (!isset($request['openid.session_type']) || !isset($request['openid.assoc_type'])) {
+ log_error('Association failed: openid.session_type or openid.assoc_type not set');
+ openid_direct_error('openid.session_type or openid.assoc_type not set');
+ return;
+ }
+
+ // Check if the assoc_type is supported
+ if (!array_key_exists($assoc_type, $assoc_types)) {
+ $error = array(
+ 'error_code' => 'unsupported-type',
+ 'session_type' => 'DH-SHA1',
+ 'assoc_type' => 'HMAC-SHA1'
+ );
+ log_error('Association failed: The association type is not supported by SimpleID.');
+ openid_direct_error('The association type is not supported by SimpleID.', $error, $version);
+ return;
+ }
+ // Check if the session_type is supported
+ if (!array_key_exists($session_type, $session_types)) {
+ $error = array(
+ 'error_code' => 'unsupported-type',
+ 'session_type' => 'DH-SHA1',
+ 'assoc_type' => 'HMAC-SHA1'
+ );
+ log_error('Association failed: The session type is not supported by SimpleID.');
+ openid_direct_error('The session type is not supported by SimpleID.', $error, $version);
+ return;
+ }
+
+ if ($session_type == 'DH-SHA1' || $session_type == 'DH-SHA256') {
+ if (!$dh_consumer_public) {
+ log_error('Association failed: openid.dh_consumer_public not set');
+ openid_direct_error('openid.dh_consumer_public not set');
+ return;
+ }
+ }
+
+ $response = _simpleid_create_association(ASSOCIATION_SHARED, $assoc_type, $session_type, $dh_modulus, $dh_gen, $dh_consumer_public);
+
+ openid_direct_response(openid_direct_message($response, $version));
+}
+
+
+/**
+ * Creates an association.
+ *
+ * This function calls {@link openid_dh_server_assoc()} where required, to
+ * generate the cryptographic values required for an association response.
+ *
+ * @param int $mode either ASSOCIATION_SHARED or ASSOCIATION_PRIVATE
+ * @param string $assoc_type a valid OpenID association type
+ * @param string $session_type a valid OpenID session type
+ * @param string $dh_modulus for Diffie-Hellman key exchange, the modulus encoded in Base64
+ * @param string $dh_gen for Diffie-Hellman key exchange, g encoded in Base64
+ * @param string $dh_consumer_public for Diffie-Hellman key exchange, the public key of the relying party encoded in Base64
+ * @return mixed if $mode is ASSOCIATION_SHARED, an OpenID response
+ * to the association request, if $mode is ASSOCIATION_PRIVATE, the
+ * association data for storage.
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor14, http://openid.net/specs/openid-authentication-2_0.html#anchor20
+ */
+function _simpleid_create_association($mode = ASSOCIATION_SHARED, $assoc_type = 'HMAC-SHA1', $session_type = 'no-encryption', $dh_modulus = NULL, $dh_gen = NULL, $dh_consumer_public = NULL) {
+ global $version;
+
+ $assoc_types = openid_association_types();
+ $session_types = openid_session_types(is_https(), $version);
+
+ $mac_size = $assoc_types[$assoc_type]['mac_size'];
+ $hmac_func = $assoc_types[$assoc_type]['hmac_func'];
+
+ $assoc_handle = random_id();
+ $expires_in = SIMPLEID_ASSOC_EXPIRES_IN;
+
+ $secret = random_bytes($mac_size);
+
+ $response = array(
+ 'assoc_handle' => $assoc_handle,
+ 'assoc_type' => $assoc_type,
+ 'expires_in' => $expires_in
+ );
+
+ // If $session_type is '', then it must be using OpenID 1.1 (blank parameter
+ // is not allowed for OpenID 2.0. For OpenID 1.1 blank requests, we don't
+ // put a session_type in the response.
+ if ($session_type != '') $response['session_type'] = $session_type;
+
+ if (($session_type == 'no-encryption') || ($session_type == '')) {
+ $mac_key = base64_encode(call_user_func($hmac_func, $secret, $response['assoc_handle']));
+ $response['mac_key'] = $mac_key;
+ } elseif ($session_type == 'DH-SHA1' || $session_type == 'DH-SHA256') {
+ $hash_func = $session_types[$session_type]['hash_func'];
+
+ $dh_assoc = openid_dh_server_assoc($secret, $dh_consumer_public, $dh_modulus, $dh_gen, $hash_func);
+ $mac_key = base64_encode($secret);
+ $response['dh_server_public'] = $dh_assoc['dh_server_public'];
+ $response['enc_mac_key'] = $dh_assoc['enc_mac_key'];
+ }
+
+ $association = array('assoc_handle' => $assoc_handle, 'assoc_type' => $assoc_type, 'mac_key' => $mac_key, 'created' => time());
+ if ($mode == ASSOCIATION_PRIVATE) $association['private'] = 1;
+ cache_set('association', $assoc_handle, $association);
+
+ if ($mode == ASSOCIATION_SHARED) {
+ log_info('Created association: ' . log_array($response));
+ log_debug('***** MAC key: ' . $association['mac_key']);
+ return $response;
+ } else {
+ log_info('Created association: private; ' . log_array($association, array('assoc_handle', 'assoc_type')));
+ log_debug('***** MAC key: ' . $association['mac_key']);
+ return $association;
+ }
+}
+
+
+/**
+ * Processes an authentication request from a relying party.
+ *
+ * An authentication request has an openid.mode value of
+ * checkid_setup or checkid_immediate.
+ *
+ * If the authentication request is a standard OpenID request about an identity
+ * (i.e. contains the key openid.identity), this function calls
+ * {@link simpleid_checkid_identity()} to see whether the user logged on into SimpleID
+ * matches the identity supplied in the OpenID request.
+ *
+ * If the authentication request is not about an identity, this function calls
+ * the {@link hook_checkid() checkid hook} of the loaded extensions.
+ *
+ * Depending on the OpenID version, this function will supply an appropriate
+ * assertion.
+ *
+ * @param array $request the OpenID request
+ *
+ */
+function simpleid_checkid($request) {
+ global $version;
+
+ $immediate = ($request['openid.mode'] == 'checkid_immediate');
+
+ log_info('OpenID authentication request: ' . (($immediate) ? 'immediate' : 'setup') . '; '. log_array($request));
+
+ // Check for protocol correctness
+ if ($version == OPENID_VERSION_1_1) {
+ if (!isset($request['openid.return_to'])) {
+ log_error('Protocol Error: openid.return_to not set.');
+ indirect_fatal_error(t('Protocol Error: openid.return_to not set.'));
+ return;
+ }
+ if (!isset($request['openid.identity'])) {
+ log_error('Protocol Error: openid.identity not set.');
+ indirect_fatal_error(t('Protocol Error: openid.identity not set.'));
+ return;
+ }
+ }
+
+ if ($version >= OPENID_VERSION_2) {
+ if (isset($request['openid.identity']) && !isset($request['openid.claimed_id'])) {
+ log_error('Protocol Error: openid.identity set, but not openid.claimed_id.');
+ indirect_fatal_error(t('Protocol Error: openid.identity set, but not openid.claimed_id.'));
+ return;
+ }
+
+ if (!isset($request['openid.realm']) && !isset($request['openid.return_to'])) {
+ log_error('Protocol Error: openid.return_to not set when openid.realm is not set.');
+ indirect_fatal_error(t('Protocol Error: openid.return_to not set when openid.realm is not set.'));
+ return;
+ }
+ }
+
+ if (isset($request['openid.return_to'])) {
+ $realm = openid_get_realm($request, $version);
+
+ if (!openid_url_matches_realm($request['openid.return_to'], $realm)) {
+ log_error('Protocol Error: openid.return_to does not match realm.');
+ openid_indirect_error($request['openid.return_to'], 'Protocol Error: openid.return_to does not match realm.');
+ return;
+ }
+ }
+
+ if (isset($request['openid.identity'])) {
+ // Standard request
+ log_debug('openid.identity found, use simpleid_checkid_identity');
+ $result = simpleid_checkid_identity($request, $immediate);
+ } else {
+ log_debug('openid.identity not found, trying extensions');
+ // Extension request
+ $results = extension_invoke_all('checkid', $request, $immediate);
+
+ // Filter out nulls
+ $results = array_merge(array_diff($results, array(NULL)));
+
+ // If there are still results, it is the lowest value, otherwise, it is CHECKID_PROTOCOL_ERROR
+ $result = ($results) ? min($results) : CHECKID_PROTOCOL_ERROR;
+ }
+
+ switch ($result) {
+ case CHECKID_APPROVAL_REQUIRED:
+ log_info('CHECKID_APPROVAL_REQUIRED');
+ if ($immediate) {
+ $response = simpleid_checkid_approval_required($request);
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ } else {
+ $response = simpleid_checkid_ok($request);
+ simpleid_openid_consent_form($request, $response, $result);
+ }
+ break;
+ case CHECKID_RETURN_TO_SUSPECT:
+ log_info('CHECKID_RETURN_TO_SUSPECT');
+ if ($immediate) {
+ $response = simpleid_checkid_error($request, $immediate);
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ } else {
+ $response = simpleid_checkid_ok($request);
+ simpleid_openid_consent_form($request, $response, $result);
+ }
+ break;
+ case CHECKID_OK:
+ log_info('CHECKID_OK');
+ $response = simpleid_checkid_ok($request);
+ $response = simpleid_sign($response, isset($request['openid.assoc_handle']) ? $request['openid.assoc_handle'] : NULL);
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ break;
+ case CHECKID_LOGIN_REQUIRED:
+ log_info('CHECKID_LOGIN_REQUIRED');
+ if ($immediate) {
+ $response = simpleid_checkid_login_required($request);
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ } else {
+ user_login_form('continue', pickle($request));
+ exit;
+ }
+ break;
+ case CHECKID_IDENTITIES_NOT_MATCHING:
+ case CHECKID_IDENTITY_NOT_EXIST:
+ log_info('CHECKID_IDENTITIES_NOT_MATCHING | CHECKID_IDENTITY_NOT_EXIST');
+ $response = simpleid_checkid_error($request, $immediate);
+ if ($immediate) {
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ } else {
+ simpleid_openid_consent_form($request, $response, $result);
+ }
+ break;
+ case CHECKID_PROTOCOL_ERROR:
+ if (isset($request['openid.return_to'])) {
+ $response = simpleid_checkid_error($request, $immediate);
+ simpleid_assertion_response($response, $request['openid.return_to']);
+ } else {
+ indirect_fatal_error('Unrecognised request.');
+ }
+ break;
+ }
+}
+
+/**
+ * Processes a standard OpenID authentication request about an identity.
+ *
+ * Checks whether the current user logged into SimpleID matches the identity
+ * supplied in an OpenID request.
+ *
+ * @param array &$request the OpenID request
+ * @param bool $immediate whether checkid_immediate was used
+ * @return int one of CHECKID_OK, CHECKID_APPROVAL_REQUIRED, CHECKID_RETURN_TO_SUSPECT, CHECKID_IDENTITY_NOT_EXIST,
+ * CHECKID_IDENTITIES_NOT_MATCHING, CHECKID_LOGIN_REQUIRED or CHECKID_PROTOCOL_ERROR
+ * @global array the current logged in user
+ */
+function simpleid_checkid_identity(&$request, $immediate) {
+ global $user, $version;
+
+ $realm = openid_get_realm($request, $version);
+
+ // Check 1: Is the user logged into SimpleID as any user?
+ if ($user == NULL) {
+ return CHECKID_LOGIN_REQUIRED;
+ } else {
+ $uid = $user['uid'];
+ }
+
+ // Check 2: Is the user logged in as the same identity as the identity requested?
+ // Choose the identity URL for the user automatically
+ if ($request['openid.identity'] == OPENID_IDENTIFIER_SELECT) {
+ $test_user = user_load($uid);
+ $identity = $test_user['identity'];
+
+ log_info('OpenID identifier selection: Selected ' . $uid . ' [' . $identity . ']');
+ } else {
+ $identity = $request['openid.identity'];
+ $test_user = user_load_from_identity($identity);
+ }
+ if ($test_user == NULL) return CHECKID_IDENTITY_NOT_EXIST;
+ if ($test_user['uid'] != $user['uid']) {
+ log_notice('Requested user ' . $test_user['uid'] . ' does not match logged in user ' . $user['uid']);
+ return CHECKID_IDENTITIES_NOT_MATCHING;
+ }
+
+ // Pass the assertion to extensions
+ $assertion_results = extension_invoke_all('checkid_identity', $request, $identity, $immediate);
+ $assertion_results = array_merge(array_diff($assertion_results, array(NULL)));
+
+ // Populate the request with the selected identity
+ if ($request['openid.identity'] == OPENID_IDENTIFIER_SELECT) {
+ $request['openid.claimed_id'] = $identity;
+ $request['openid.identity'] = $identity;
+ }
+
+ // Check 3: Discover the realm and match its return_to
+ $user_rp = (isset($user['rp'][$realm])) ? $user['rp'][$realm] : NULL;
+
+ if (($version >= OPENID_VERSION_2) && SIMPLEID_VERIFY_RETURN_URL_USING_REALM) {
+ $verified = FALSE;
+
+ $rp_info = simpleid_get_rp_info($realm);
+ $services = discovery_xrds_services_by_type($rp_info['services'], OPENID_RETURN_TO);
+
+ log_info('OpenID 2 discovery: ' . count($services) . ' matching services');
+
+ if ($services) {
+ $return_to_uris = array();
+
+ foreach ($services as $service) {
+ $return_to_uris = array_merge($return_to_uris, $service['uri']);
+ }
+ foreach ($return_to_uris as $return_to) {
+ if (openid_url_matches_realm($request['openid.return_to'], $return_to)) {
+ log_info('OpenID 2 discovery: verified');
+ $verified = TRUE;
+ break;
+ }
+ }
+ }
+
+ $rp_info['return_to_verified'] = $verified;
+ simpleid_set_rp_info($realm, $rp_info);
+
+ if (!$verified) {
+ if (($user_rp != NULL) && ($user_rp['auto_release'] == 1)) {
+ log_notice('OpenID 2 discovery: not verified, but overridden by user preference');
+ } else {
+ log_notice('OpenID 2 discovery: not verified');
+ $assertion_results[] = CHECKID_RETURN_TO_SUSPECT;
+ }
+ }
+ }
+
+ // Check 4: For checkid_immediate, the user must already have given
+ // permission to log in automatically.
+ if (($user_rp != NULL) && ($user_rp['auto_release'] == 1)) {
+ log_info('Automatic set for realm ' . $realm);
+ $assertion_results[] = CHECKID_OK;
+ return min($assertion_results);
+ } else {
+ $assertion_results[] = CHECKID_APPROVAL_REQUIRED;
+ return min($assertion_results);
+ }
+}
+
+/**
+ * Obtains information on a relying party by performing discovery on them. Information
+ * obtained includes the discovery URL, the parsed XRDS document, and any other
+ * information saved by SimpleID extensions
+ *
+ * The results are cached for 1 hour. For performance reasons, stale results may
+ * be obtained by using the $allow_stale parameter
+ *
+ * @param string $realm the openid.realm parameter
+ * @param bool $allow_stale allow stale results to be returned, otherwise discovery
+ * will occur
+ * @return array containing information on a relying party.
+ * @link http://openid.net/specs/openid-authentication-2_0.html#rp_discovery
+ * @since 0.8
+ */
+function simpleid_get_rp_info($realm, $allow_stale = FALSE) {
+ $url = openid_realm_discovery_url($realm);
+
+ log_info('simpleid_get_rp_info');
+
+ $rp_info = cache_get('rp-info', $realm);
+
+ if (($rp_info == NULL) || (!isset($rp_info['updated'])) || (!$allow_stale && ($rp_info['updated'] < time() - 3600))) {
+ log_info('OpenID 2 RP discovery: realm: ' . $realm . '; url: ' . $url);
+
+ $rp_info = array(
+ 'url' => $url,
+ 'services' => discovery_xrds_discover($url),
+ 'updated' => time()
+ );
+
+ cache_set('rp-info', $realm, $rp_info);
+ }
+
+ return $rp_info;
+}
+
+/**
+ * Saves information on a relying party to disk.
+ *
+ * @param string $realm the openid.realm parameter
+ * @param array $rp_info containing information on a relying party.
+ *
+ * @since 0.8
+ */
+function simpleid_set_rp_info($realm, $rp_info) {
+ if (!isset($rp_info['updated'])) $rp_info['updated'] = time();
+ cache_set('rp-info', $realm, $rp_info, $rp_info['updated']);
+}
+
+/**
+ * Returns an OpenID response indicating a positive assertion.
+ *
+ * @param array $request the OpenID request
+ * @return array an OpenID response with a positive assertion
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#positive_assertions
+ */
+function simpleid_checkid_ok($request) {
+ global $version;
+
+ $message = array(
+ 'openid.mode' => 'id_res',
+ 'openid.op_endpoint' => simpleid_url(),
+ 'openid.response_nonce' => openid_nonce()
+ );
+
+ if (isset($request['openid.assoc_handle'])) $message['openid.assoc_handle'] = $request['openid.assoc_handle'];
+ if (isset($request['openid.identity'])) $message['openid.identity'] = $request['openid.identity'];
+ if (isset($request['openid.return_to'])) $message['openid.return_to'] = $request['openid.return_to'];
+
+ if (($version >= OPENID_VERSION_2) && isset($request['openid.claimed_id'])) {
+ $message['openid.claimed_id'] = $request['openid.claimed_id'];
+ }
+
+ $message = array_merge($message, extension_invoke_all('response', TRUE, $request));
+
+ log_info('OpenID authentication response: ' . log_array($message));
+ return openid_indirect_message($message, $version);
+}
+
+/**
+ * Returns an OpenID response indicating a negative assertion to a
+ * checkid_immediate request, where an approval of the relying party by the
+ * user is required
+ *
+ * @param mixed $request the OpenID request
+ * @return mixed an OpenID response with a negative assertion
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions
+ */
+function simpleid_checkid_approval_required($request) {
+ global $version;
+
+ if ($version >= OPENID_VERSION_2) {
+ $message = array('openid.mode' => 'setup_needed');
+ } else {
+ $request['openid.mode'] = 'checkid_setup';
+ $message = array(
+ 'openid.mode' => 'id_res',
+ 'openid.user_setup_url' => simpleid_url('continue', 's=' . rawurlencode(pickle($request)))
+ );
+ }
+
+ $message = array_merge($message, extension_invoke_all('response', FALSE, $request));
+
+ log_info('OpenID authentication response: ' . log_array($message));
+ return openid_indirect_message($message, $version);
+}
+
+/**
+ * Returns an OpenID response indicating a negative assertion to a
+ * checkid_immediate request, where the user has not logged in.
+ *
+ * @param array $request the OpenID request
+ * @return array an OpenID response with a negative assertion
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions
+ */
+function simpleid_checkid_login_required($request) {
+ global $version;
+
+ if ($version >= OPENID_VERSION_2) {
+ $message = array('openid.mode' => 'setup_needed');
+ } else {
+ $message = array(
+ 'openid.mode' => 'id_res',
+ 'openid.user_setup_url' => simpleid_url('login', 'destination=continue&s=' . rawurlencode(pickle($request)))
+ );
+ }
+
+ $message = array_merge($message, extension_invoke_all('response', FALSE, $request));
+
+ log_info('OpenID authentication response: ' . log_array($message));
+ return openid_indirect_message($message, $version);
+}
+
+/**
+ * Returns an OpenID response indicating a generic negative assertion.
+ *
+ * The content of the negative version depends on the OpenID version, and whether
+ * the openid.mode of the request was checkid_immediate
+ *
+ * @param array $request the OpenID request
+ * @param bool $immediate true if openid.mode of the request was checkid_immediate
+ * @return array an OpenID response with a negative assertion
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions
+ */
+function simpleid_checkid_error($request, $immediate = false) {
+ global $version;
+
+ $message = array();
+ if ($immediate) {
+ if ($version >= OPENID_VERSION_2) {
+ $message['openid.mode'] = 'setup_needed';
+ } else {
+ $message['openid.mode'] = 'id_res';
+ }
+ } else {
+ $message['openid.mode'] = 'cancel';
+ }
+
+ $message = array_merge($message, extension_invoke_all('response', FALSE, $request));
+
+ log_info('OpenID authentication response: ' . log_array($message));
+ return openid_indirect_message($message, $version);
+}
+
+/**
+ * Signs an OpenID response, using signature information from an association
+ * handle.
+ *
+ * @param array &$response the OpenID response
+ * @param array $assoc_handle the association handle containing key information
+ * for the signature. If $assoc_handle is not specified, a private association
+ * is created
+ * @return array the signed OpenID response
+ *
+ */
+function simpleid_sign(&$response, $assoc_handle = NULL) {
+ global $version;
+
+ if (!$assoc_handle) {
+ $assoc = _simpleid_create_association(ASSOCIATION_PRIVATE);
+ $response['openid.assoc_handle'] = $assoc['assoc_handle'];
+ } else {
+ $assoc = cache_get('association', $assoc_handle);
+
+ if (!is_array($assoc) || ($assoc['created'] + SIMPLEID_ASSOC_EXPIRES_IN < time())) {
+ // Association has expired, need to create a new one
+ log_notice('Association handle ' . ($assoc['assoc_handle'] ?? '(none)') . ' expired. Using stateless mode.');
+ $response['openid.invalidate_handle'] = $assoc_handle;
+ $assoc = _simpleid_create_association(ASSOCIATION_PRIVATE);
+ $response['openid.assoc_handle'] = $assoc['assoc_handle'];
+ }
+ }
+
+ // If we are using stateless mode, then we need to cache the response_nonce
+ // so that the RP can only verify once
+ if (isset($assoc['private']) && ($assoc['private'] == 1) && isset($response['openid.response_nonce'])) {
+ cache_set('stateless', $response['openid.response_nonce'], array(
+ 'response_nonce' => $response['openid.response_nonce'],
+ 'assoc_handle' => $response['openid.assoc_handle']));
+ }
+
+ // Get all the signed fields [10.1]
+ openid_parse_request($response); // Fill the namespace array
+ $signed_fields = array('op_endpoint', 'return_to', 'response_nonce', 'assoc_handle', 'identity', 'claimed_id');
+ $signed_fields = array_merge($signed_fields, extension_invoke_all('signed_fields', $response));
+
+ // Check if the signed keys are actually present
+ $to_sign = array();
+ foreach ($signed_fields as $field) {
+ if (isset($response['openid.' . $field])) $to_sign[] = $field;
+ }
+
+ $response['openid.signed'] = implode(',', $to_sign);
+
+ // Generate signature for this message
+ $mac_key = $assoc['mac_key'];
+ $assoc_types = openid_association_types();
+ $hmac_func = $assoc_types[$assoc['assoc_type']]['hmac_func'];
+
+ $response['openid.sig'] = openid_sign($response, $to_sign, $mac_key, $hmac_func, $version);
+
+ log_info('OpenID signed authentication response: ' . log_array($response));
+
+ return $response;
+}
+
+/**
+ * Processes a direct verification request. This is used in the OpenID specification
+ * to verify signatures generated using stateless mode.
+ *
+ * @param array $request the OpenID request
+ * @see http://openid.net/specs/openid-authentication-1_1.html#mode_check_authentication, http://openid.net/specs/openid-authentication-2_0.html#verifying_signatures
+ */
+function simpleid_check_authentication($request) {
+ global $version;
+
+ log_info('OpenID direct verification: ' . log_array($request));
+
+ $is_valid = simpleid_verify_signatures($request);
+
+ if ($is_valid) {
+ $response = array('is_valid' => 'true');
+ } else {
+ $response = array('is_valid' => 'false');
+ }
+
+ // RP wants to check whether a handle is invalid
+ if (isset($request['openid.invalidate_handle'])) {
+ $invalid_assoc = cache_get('association', $request['openid.invalidate_handle']);
+
+ if (!$invalid_assoc || ($invalid_assoc['created'] + SIMPLEID_ASSOC_EXPIRES_IN < time())) {
+ // Yes, it's invalid
+ $response['invalidate_handle'] = $request['openid.invalidate_handle'];
+ }
+ }
+
+ log_info('OpenID direct verification response: ' . log_array($response));
+
+ openid_direct_response(openid_direct_message($response, $version));
+}
+
+/**
+ * Verifies the signature of a signed OpenID request/response.
+ *
+ * @param array $request the OpenID request/response
+ * @return bool true if the signature is verified
+ * @since 0.8
+ */
+function simpleid_verify_signatures($request) {
+ global $version;
+
+ log_info('simpleid_verify_signatures');
+
+ $is_valid = TRUE;
+
+ $assoc = (isset($request['openid.assoc_handle'])) ? cache_get('association', $request['openid.assoc_handle']) : NULL;
+ $stateless = (isset($request['openid.response_nonce'])) ? cache_get('stateless', $request['openid.response_nonce']) : NULL;
+
+ if (!$assoc) {
+ log_notice('simpleid_verify_signatures: Association not found.');
+ $is_valid = FALSE;
+ } elseif (!$assoc['assoc_type']) {
+ log_error('simpleid_verify_signatures: Association does not contain valid assoc_type.');
+ $is_valid = FALSE;
+ } elseif (!isset($assoc['private']) || ($assoc['private'] != 1)) {
+ log_warn('simpleid_verify_signatures: Attempting to verify an association with a shared key.');
+ $is_valid = FALSE;
+ } elseif (!$stateless || ($stateless['assoc_handle'] != $request['openid.assoc_handle'])) {
+ log_warn('simpleid_verify_signatures: Attempting to verify a response_nonce more than once, or private association expired.');
+ $is_valid = FALSE;
+ } else {
+ $mac_key = $assoc['mac_key'];
+ $assoc_types = openid_association_types();
+ $hmac_func = $assoc_types[$assoc['assoc_type']]['hmac_func'];
+
+ $signed_keys = explode(',', $request['openid.signed']);
+ $signature = openid_sign($request, $signed_keys, $mac_key, $hmac_func, $version);
+ log_debug('***** Signature: ' . $signature);
+
+ if ($signature != $request['openid.sig']) {
+ log_warn('simpleid_verify_signatures: Signature supplied in request does not match the signatured generated.');
+ $is_valid = FALSE;
+ }
+
+ cache_delete('stateless', $request['openid.response_nonce']);
+ }
+
+ return $is_valid;
+}
+
+
+/**
+ * Continues an OpenID authentication request.
+ *
+ * This function decodes an OpenID authentication request specified in the
+ * s request parameter and feeds it to the
+ * {@link simpleid_process_openid} function. This allows SimpleID to preserve
+ * the state of an OpenID request.
+ */
+function simpleid_continue() {
+ global $GETPOST;
+
+ $request = unpickle($GETPOST['s']);
+ openid_parse_request($request);
+ simpleid_process_openid($request);
+}
+
+/**
+ * Provides a form for user consent of an OpenID relying party, where the
+ * {@link simpleid_checkid_identity()} function returns a CHECKID_APPROVAL_REQUIRED
+ * or CHECKID_RETURN_TO_SUSPECT.
+ *
+ * Alternatively, provide a form for the user to rectify the situation where
+ * {@link simpleid_checkid_identity()} function returns a CHECKID_IDENTITIES_NOT_MATCHING
+ * or CHECKID_IDENTITY_NOT_EXIST
+ *
+ * @param array $request the original OpenID request
+ * @param array $response the proposed OpenID response, subject to user
+ * verification
+ * @param int $reason either CHECKID_APPROVAL_REQUIRED, CHECKID_RETURN_TO_SUSPECT,
+ * CHECKID_IDENTITIES_NOT_MATCHING or CHECKID_IDENTITY_NOT_EXIST
+ */
+function simpleid_openid_consent_form($request, $response, $reason = CHECKID_APPROVAL_REQUIRED) {
+ global $user;
+ global $xtpl;
+ global $version;
+
+ $request_state = pickle($request);
+
+ user_header($request_state);
+
+ $realm = openid_get_realm($request, $version);
+
+ $xtpl->assign('token', get_form_token('rp'));
+ $xtpl->assign('state', pickle($response));
+ $xtpl->assign('realm', htmlspecialchars($realm, ENT_QUOTES, 'UTF-8'));
+
+ $xtpl->assign('cancel_button', t('Cancel'));
+
+ if ($response['openid.mode'] == 'cancel') {
+ $xtpl->assign('return_to', htmlspecialchars($request['openid.return_to'], ENT_QUOTES, 'UTF-8'));
+
+ $xtpl->assign('unable_label', t('Unable to log into <strong class="realm">@realm</strong>.', array('@realm' => $realm)));
+ $xtpl->assign('identity_not_matching_label', t('Your current identity does not match the requested identity %identity.', array('%identity' => $request['openid.identity'])));
+ $xtpl->assign('switch_user_label', t('<a href="!url">Switch to a different user</a> and try again.', array('!url' => simpleid_url('logout', 'destination=continue&s=' . rawurlencode($request_state), true))));
+
+ $xtpl->parse('main.openid_consent.cancel');
+ } else {
+ $xtpl->assign('javascript', '<script src="' . get_base_path() . 'html/openid-consent.js" type="text/javascript"></script>');
+
+ $rp = (isset($user['rp'][$realm])) ? $user['rp'][$realm] : NULL;
+
+ $extensions = extension_invoke_all('consent_form', $request, $response, $rp);
+ $xtpl->assign('extensions', implode($extensions));
+
+ if ($reason == CHECKID_RETURN_TO_SUSPECT) {
+ $xtpl->assign('suspect_label', t('Warning: This web site has not confirmed its identity and might be fraudulent. Do not share any personal information with this web site unless you are sure it is legitimate. See the <a href="!url" class="popup">SimpleID documentation for details</a> (OpenID version 2.0 return_to discovery failure)',
+ array('!url' => 'http://simpleid.org/docs/1/return_to/')));
+
+ $xtpl->parse('main.openid_consent.setup.suspect');
+ $xtpl->assign('realm_class', 'return-to-suspect');
+ }
+
+ $xtpl->assign('realm_label', t('You are being logged into <strong class="realm">@realm</strong>.', array('@realm' => $realm)));
+ $xtpl->assign('auto_release_label', t('Automatically send my information to this site for any future requests.'));
+ $xtpl->assign('ok_button', t('OK'));
+
+ $xtpl->parse('main.openid_consent.setup');
+ }
+
+ $xtpl->parse('main.openid_consent');
+
+ $xtpl->assign(array('js_locale_label' => 'openid_suspect', 'js_locale_text' => addslashes(t('This web site has not confirmed its identity and might be fraudulent.')) . '\n\n' . addslashes(t('Are you sure you wish to automatically send your information to this site for any future requests?'))));
+ $xtpl->parse('main.js_locale');
+
+ $xtpl->parse('main.framekiller');
+
+ header('X-Frame-Options: DENY');
+
+ $xtpl->assign('title', t('OpenID Login'));
+ $xtpl->assign('page_class', 'dialog-page');
+ $xtpl->parse('main');
+
+ $xtpl->out('main');
+}
+
+
+/**
+ * Processes a user response from the {@link simpleid_openid_consent_form()} function.
+ *
+ * If the user verifies the relying party, an OpenID response will be sent to
+ * the relying party. Otherwise, the dashboard will be displayed to the user.
+ *
+ */
+function simpleid_openid_consent() {
+ global $xtpl, $user, $version, $GETPOST;
+
+ if ($user == NULL) {
+ user_login_form('');
+ return;
+ }
+
+ if (!validate_form_token($GETPOST['tk'], 'rp')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ $xtpl->assign('title', t('OpenID Login'));
+ $xtpl->parse('main');
+ $xtpl->out('main');
+ return;
+ }
+
+ $uid = $user['uid'];
+
+ $response = unpickle($GETPOST['s']);
+ $version = openid_get_version($response);
+ openid_parse_request($response);
+ $return_to = $response['openid.return_to'];
+ if (!$return_to) $return_to = $GETPOST['openid.return_to'];
+
+ if ($GETPOST['op'] == t('Cancel')) {
+ $response = simpleid_checkid_error(false);
+ if (!$return_to) set_message(t('Log in cancelled.'));
+ } else {
+ $now = time();
+ $realm = $GETPOST['openid.realm'];
+
+ if (isset($user['rp'][$realm])) {
+ $rp = $user['rp'][$realm];
+ } else {
+ $rp = array('realm' => $realm, 'first_time' => $now);
+ }
+ $rp['last_time'] = $now;
+ $rp['auto_release'] = (isset($GETPOST['autorelease']) && $GETPOST['autorelease']) ? 1 : 0;
+
+ // Mimic extension_invoke_all, but allow for passing by reference {{
+ global $simpleid_extensions;
+
+ foreach ($simpleid_extensions as $extension) {
+ $consent_function = $extension . '_consent';
+ if (function_exists($consent_function)) {
+ $consent_function($GETPOST, $response, $rp);
+ }
+ }
+ // }}
+
+ $user['rp'][$realm] = $rp;
+ user_save($user);
+
+ $response = simpleid_sign($response, isset($response['openid.assoc_handle']) ? $response['openid.assoc_handle'] : NULL);
+ if (!$return_to) set_message(t('You were logged in successfully.'));
+ }
+
+ if ($return_to) {
+ simpleid_assertion_response($response, $return_to);
+ } else {
+ page_dashboard();
+ }
+}
+
+/**
+ * Sends an OpenID assertion response.
+ *
+ * The OpenID specification version 2.0 provides for the sending of assertions
+ * via indirect communication. However, future versions of the OpenID
+ * specification may provide for sending of assertions via direct communication.
+ *
+ * @param array $response the signed OpenID assertion response to send
+ * @param string $indirect_url the URL to which the OpenID response is sent. If
+ * this is an empty string, the response is sent via direct communication
+ */
+function simpleid_assertion_response($response, $indirect_url = NULL) {
+ global $xtpl, $version;
+
+ if ($indirect_url) {
+ // We want to see if the extensions want to change the way indirect responses are made
+ $results = extension_invoke_all('indirect_response', $indirect_url, $response);
+ $results = array_filter($results, 'is_null');
+ $component = ($results) ? max($results) : OPENID_RESPONSE_QUERY;
+
+ openid_indirect_response($indirect_url, $response, $component);
+ } else {
+ openid_direct_response(openid_direct_message($response, $version));
+ }
+}
+
+/**
+ * Displays the XRDS document for this SimpleID installation.
+ *
+ */
+function simpleid_xrds() {
+ global $xtpl;
+
+ log_debug('Providing XRDS.');
+
+ header('Content-Type: application/xrds+xml');
+ header('Content-Disposition: inline; filename=yadis.xml');
+
+ $types = extension_invoke_all('xrds_types');
+ foreach ($types as $type) {
+ $xtpl->assign('uri', htmlspecialchars($type, ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('xrds.op_xrds.type');
+ }
+
+ $xtpl->assign('simpleid_base_url', htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8'), false, 'detect');
+ $xtpl->parse('xrds.op_xrds');
+ $xtpl->parse('xrds');
+ $xtpl->out('xrds');
+}
+
+
+?>
diff --git a/simpleid/www/lib/gettext/gettext.inc.php b/simpleid/www/lib/gettext/gettext.inc.php
new file mode 100644
index 0000000..00b9666
--- /dev/null
+++ b/simpleid/www/lib/gettext/gettext.inc.php
@@ -0,0 +1,536 @@
+<?php
+/*
+ Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
+ Copyright (c) 2009 Danilo Segan <danilo@kvota.net>
+
+ Drop in replacement for native gettext.
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+/*
+LC_CTYPE 0
+LC_NUMERIC 1
+LC_TIME 2
+LC_COLLATE 3
+LC_MONETARY 4
+LC_MESSAGES 5
+LC_ALL 6
+*/
+
+// LC_MESSAGES is not available if php-gettext is not loaded
+// while the other constants are already available from session extension.
+if (!defined('LC_MESSAGES')) {
+ define('LC_MESSAGES', 5);
+}
+
+require('streams.php');
+require('gettext.php');
+
+
+// Variables
+
+global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
+$text_domains = array();
+$default_domain = 'messages';
+$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
+$EMULATEGETTEXT = 0;
+$CURRENTLOCALE = '';
+
+/* Class to hold a single domain included in $text_domains. */
+class domain {
+ var $l10n;
+ var $path;
+ var $codeset;
+}
+
+// Utility functions
+
+/**
+ * Return a list of locales to try for any POSIX-style locale specification.
+ */
+function get_list_of_locales($locale) {
+ /* Figure out all possible locale names and start with the most
+ * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of
+ * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
+ */
+ $locale_names = array();
+ $lang = NULL;
+ $country = NULL;
+ $charset = NULL;
+ $modifier = NULL;
+ if ($locale) {
+ if (preg_match("/^(?P<lang>[a-z]{2,3})" // language code
+ ."(?:_(?P<country>[A-Z]{2}))?" // country code
+ ."(?:\.(?P<charset>[-A-Za-z0-9_]+))?" // charset
+ ."(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/", // @ modifier
+ $locale, $matches)) {
+
+ if (isset($matches["lang"])) $lang = $matches["lang"];
+ if (isset($matches["country"])) $country = $matches["country"];
+ if (isset($matches["charset"])) $charset = $matches["charset"];
+ if (isset($matches["modifier"])) $modifier = $matches["modifier"];
+
+ if ($modifier) {
+ if ($country) {
+ if ($charset)
+ array_push($locale_names, "${lang}_$country.$charset@$modifier");
+ array_push($locale_names, "${lang}_$country@$modifier");
+ } elseif ($charset)
+ array_push($locale_names, "${lang}.$charset@$modifier");
+ array_push($locale_names, "$lang@$modifier");
+ }
+ if ($country) {
+ if ($charset)
+ array_push($locale_names, "${lang}_$country.$charset");
+ array_push($locale_names, "${lang}_$country");
+ } elseif ($charset)
+ array_push($locale_names, "${lang}.$charset");
+ array_push($locale_names, $lang);
+ }
+
+ // If the locale name doesn't match POSIX style, just include it as-is.
+ if (!in_array($locale, $locale_names))
+ array_push($locale_names, $locale);
+ }
+ return $locale_names;
+}
+
+/**
+ * Utility function to get a StreamReader for the given text domain.
+ */
+function _get_reader($domain=null, $category=5, $enable_cache=true) {
+ global $text_domains, $default_domain, $LC_CATEGORIES;
+ if (!isset($domain)) $domain = $default_domain;
+ if (!isset($text_domains[$domain]->l10n)) {
+ // get the current locale
+ $locale = _setlocale(LC_MESSAGES, 0);
+ $bound_path = isset($text_domains[$domain]->path) ?
+ $text_domains[$domain]->path : './';
+ $subpath = $LC_CATEGORIES[$category] ."/$domain.mo";
+
+ $locale_names = get_list_of_locales($locale);
+ $input = null;
+ foreach ($locale_names as $locale) {
+ $full_path = $bound_path . $locale . "/" . $subpath;
+ if (file_exists($full_path)) {
+ $input = new FileReader($full_path);
+ break;
+ }
+ }
+
+ if (!array_key_exists($domain, $text_domains)) {
+ // Initialize an empty domain object.
+ $text_domains[$domain] = new domain();
+ }
+ $text_domains[$domain]->l10n = new gettext_reader($input,
+ $enable_cache);
+ }
+ return $text_domains[$domain]->l10n;
+}
+
+/**
+ * Returns whether we are using our emulated gettext API or PHP built-in one.
+ */
+function locale_emulation() {
+ global $EMULATEGETTEXT;
+ return $EMULATEGETTEXT;
+}
+
+/**
+ * Checks if the current locale is supported on this system.
+ */
+function _check_locale_and_function($function=false) {
+ global $EMULATEGETTEXT;
+ if ($function and !function_exists($function))
+ return false;
+ return !$EMULATEGETTEXT;
+}
+
+/**
+ * Get the codeset for the given domain.
+ */
+function _get_codeset($domain=null) {
+ global $text_domains, $default_domain, $LC_CATEGORIES;
+ if (!isset($domain)) $domain = $default_domain;
+ return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
+}
+
+/**
+ * Convert the given string to the encoding set by bind_textdomain_codeset.
+ */
+function _encode($text) {
+ $source_encoding = mb_detect_encoding($text);
+ $target_encoding = _get_codeset();
+ if ($source_encoding != $target_encoding) {
+ return mb_convert_encoding($text, $target_encoding, $source_encoding);
+ }
+ else {
+ return $text;
+ }
+}
+
+
+// Custom implementation of the standard gettext related functions
+
+/**
+ * Returns passed in $locale, or environment variable $LANG if $locale == ''.
+ */
+function _get_default_locale($locale) {
+ if ($locale == '') // emulate variable support
+ return getenv('LANG');
+ else
+ return $locale;
+}
+
+/**
+ * Sets a requested locale, if needed emulates it.
+ */
+function _setlocale($category, $locale) {
+ global $CURRENTLOCALE, $EMULATEGETTEXT;
+ if ($locale === 0) { // use === to differentiate between string "0"
+ if ($CURRENTLOCALE != '')
+ return $CURRENTLOCALE;
+ else
+ // obey LANG variable, maybe extend to support all of LC_* vars
+ // even if we tried to read locale without setting it first
+ return _setlocale($category, $CURRENTLOCALE);
+ } else {
+ if (function_exists('setlocale')) {
+ $ret = setlocale($category, $locale);
+ if (($locale == '' and !$ret) or // failed setting it by env
+ ($locale != '' and $ret != $locale)) { // failed setting it
+ // Failed setting it according to environment.
+ $CURRENTLOCALE = _get_default_locale($locale);
+ $EMULATEGETTEXT = 1;
+ } else {
+ $CURRENTLOCALE = $ret;
+ $EMULATEGETTEXT = 0;
+ }
+ } else {
+ // No function setlocale(), emulate it all.
+ $CURRENTLOCALE = _get_default_locale($locale);
+ $EMULATEGETTEXT = 1;
+ }
+ // Allow locale to be changed on the go for one translation domain.
+ global $text_domains, $default_domain;
+ if (array_key_exists($default_domain, $text_domains)) {
+ unset($text_domains[$default_domain]->l10n);
+ }
+ return $CURRENTLOCALE;
+ }
+}
+
+/**
+ * Sets the path for a domain.
+ */
+function _bindtextdomain($domain, $path) {
+ global $text_domains;
+ // ensure $path ends with a slash ('/' should work for both, but lets still play nice)
+ if (substr(php_uname(), 0, 7) == "Windows") {
+ if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/')
+ $path .= '\\';
+ } else {
+ if ($path[strlen($path)-1] != '/')
+ $path .= '/';
+ }
+ if (!array_key_exists($domain, $text_domains)) {
+ // Initialize an empty domain object.
+ $text_domains[$domain] = new domain();
+ }
+ $text_domains[$domain]->path = $path;
+}
+
+/**
+ * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
+ */
+function _bind_textdomain_codeset($domain, $codeset) {
+ global $text_domains;
+ $text_domains[$domain]->codeset = $codeset;
+}
+
+/**
+ * Sets the default domain.
+ */
+function _textdomain($domain) {
+ global $default_domain;
+ $default_domain = $domain;
+}
+
+/**
+ * Lookup a message in the current domain.
+ */
+function _gettext($msgid) {
+ $l10n = _get_reader();
+ return _encode($l10n->translate($msgid));
+}
+
+/**
+ * Alias for gettext.
+ */
+function __($msgid) {
+ return _gettext($msgid);
+}
+
+/**
+ * Plural version of gettext.
+ */
+function _ngettext($singular, $plural, $number) {
+ $l10n = _get_reader();
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Override the current domain.
+ */
+function _dgettext($domain, $msgid) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->translate($msgid));
+}
+
+/**
+ * Plural version of dgettext.
+ */
+function _dngettext($domain, $singular, $plural, $number) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Overrides the domain and category for a single lookup.
+ */
+function _dcgettext($domain, $msgid, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dcgettext.
+ */
+function _dcngettext($domain, $singular, $plural, $number, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Context version of gettext.
+ */
+function _pgettext($context, $msgid) {
+ $l10n = _get_reader();
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Override the current domain in a context gettext call.
+ */
+function _dpgettext($domain, $context, $msgid) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Overrides the domain and category for a single context-based lookup.
+ */
+function _dcpgettext($domain, $context, $msgid, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Context version of ngettext.
+ */
+function _npgettext($context, $singular, $plural) {
+ $l10n = _get_reader();
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Override the current domain in a context ngettext call.
+ */
+function _dnpgettext($domain, $context, $singular, $plural) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Overrides the domain and category for a plural context-based lookup.
+ */
+function _dcnpgettext($domain, $context, $singular, $plural, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+
+
+// Wrappers to use if the standard gettext functions are available,
+// but the current locale is not supported by the system.
+// Use the standard impl if the current locale is supported, use the
+// custom impl otherwise.
+
+function T_setlocale($category, $locale) {
+ return _setlocale($category, $locale);
+}
+
+function T_bindtextdomain($domain, $path) {
+ if (_check_locale_and_function()) return bindtextdomain($domain, $path);
+ else return _bindtextdomain($domain, $path);
+}
+function T_bind_textdomain_codeset($domain, $codeset) {
+ // bind_textdomain_codeset is available only in PHP 4.2.0+
+ if (_check_locale_and_function('bind_textdomain_codeset'))
+ return bind_textdomain_codeset($domain, $codeset);
+ else return _bind_textdomain_codeset($domain, $codeset);
+}
+function T_textdomain($domain) {
+ if (_check_locale_and_function()) return textdomain($domain);
+ else return _textdomain($domain);
+}
+function T_gettext($msgid) {
+ if (_check_locale_and_function()) return gettext($msgid);
+ else return _gettext($msgid);
+}
+function T_($msgid) {
+ if (_check_locale_and_function()) return _($msgid);
+ return __($msgid);
+}
+function T_ngettext($singular, $plural, $number) {
+ if (_check_locale_and_function())
+ return ngettext($singular, $plural, $number);
+ else return _ngettext($singular, $plural, $number);
+}
+function T_dgettext($domain, $msgid) {
+ if (_check_locale_and_function()) return dgettext($domain, $msgid);
+ else return _dgettext($domain, $msgid);
+}
+function T_dngettext($domain, $singular, $plural, $number) {
+ if (_check_locale_and_function())
+ return dngettext($domain, $singular, $plural, $number);
+ else return _dngettext($domain, $singular, $plural, $number);
+}
+function T_dcgettext($domain, $msgid, $category) {
+ if (_check_locale_and_function())
+ return dcgettext($domain, $msgid, $category);
+ else return _dcgettext($domain, $msgid, $category);
+}
+function T_dcngettext($domain, $singular, $plural, $number, $category) {
+ if (_check_locale_and_function())
+ return dcngettext($domain, $singular, $plural, $number, $category);
+ else return _dcngettext($domain, $singular, $plural, $number, $category);
+}
+
+function T_pgettext($context, $msgid) {
+ if (_check_locale_and_function('pgettext'))
+ return pgettext($context, $msgid);
+ else
+ return _pgettext($context, $msgid);
+}
+
+function T_dpgettext($domain, $context, $msgid) {
+ if (_check_locale_and_function('dpgettext'))
+ return dpgettext($domain, $context, $msgid);
+ else
+ return _dpgettext($domain, $context, $msgid);
+}
+
+function T_dcpgettext($domain, $context, $msgid, $category) {
+ if (_check_locale_and_function('dcpgettext'))
+ return dcpgettext($domain, $context, $msgid, $category);
+ else
+ return _dcpgettext($domain, $context, $msgid, $category);
+}
+
+function T_npgettext($context, $singular, $plural, $number) {
+ if (_check_locale_and_function('npgettext'))
+ return npgettext($context, $singular, $plural, $number);
+ else
+ return _npgettext($context, $singular, $plural, $number);
+}
+
+function T_dnpgettext($domain, $context, $singular, $plural, $number) {
+ if (_check_locale_and_function('dnpgettext'))
+ return dnpgettext($domain, $context, $singular, $plural, $number);
+ else
+ return _dnpgettext($domain, $context, $singular, $plural, $number);
+}
+
+function T_dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category) {
+ if (_check_locale_and_function('dcnpgettext'))
+ return dcnpgettext($domain, $context, $singular,
+ $plural, $number, $category);
+ else
+ return _dcnpgettext($domain, $context, $singular,
+ $plural, $number, $category);
+}
+
+
+
+// Wrappers used as a drop in replacement for the standard gettext functions
+
+if (!function_exists('gettext')) {
+ function bindtextdomain($domain, $path) {
+ return _bindtextdomain($domain, $path);
+ }
+ function bind_textdomain_codeset($domain, $codeset) {
+ return _bind_textdomain_codeset($domain, $codeset);
+ }
+ function textdomain($domain) {
+ return _textdomain($domain);
+ }
+ function gettext($msgid) {
+ return _gettext($msgid);
+ }
+ function _($msgid) {
+ return __($msgid);
+ }
+ function ngettext($singular, $plural, $number) {
+ return _ngettext($singular, $plural, $number);
+ }
+ function dgettext($domain, $msgid) {
+ return _dgettext($domain, $msgid);
+ }
+ function dngettext($domain, $singular, $plural, $number) {
+ return _dngettext($domain, $singular, $plural, $number);
+ }
+ function dcgettext($domain, $msgid, $category) {
+ return _dcgettext($domain, $msgid, $category);
+ }
+ function dcngettext($domain, $singular, $plural, $number, $category) {
+ return _dcngettext($domain, $singular, $plural, $number, $category);
+ }
+ function pgettext($context, $msgid) {
+ return _pgettext($context, $msgid);
+ }
+ function npgettext($context, $singular, $plural, $number) {
+ return _npgettext($context, $singular, $plural, $number);
+ }
+ function dpgettext($domain, $context, $msgid) {
+ return _dpgettext($domain, $context, $msgid);
+ }
+ function dnpgettext($domain, $context, $singular, $plural, $number) {
+ return _dnpgettext($domain, $context, $singular, $plural, $number);
+ }
+ function dcpgettext($domain, $context, $msgid, $category) {
+ return _dcpgettext($domain, $context, $msgid, $category);
+ }
+ function dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category) {
+ return _dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category);
+ }
+}
+
+?>
diff --git a/simpleid/www/lib/gettext/gettext.php b/simpleid/www/lib/gettext/gettext.php
new file mode 100644
index 0000000..5064047
--- /dev/null
+++ b/simpleid/www/lib/gettext/gettext.php
@@ -0,0 +1,432 @@
+<?php
+/*
+ Copyright (c) 2003, 2009 Danilo Segan <danilo@kvota.net>.
+ Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+/**
+ * Provides a simple gettext replacement that works independently from
+ * the system's gettext abilities.
+ * It can read MO files and use them for translating strings.
+ * The files are passed to gettext_reader as a Stream (see streams.php)
+ *
+ * This version has the ability to cache all strings and translations to
+ * speed up the string lookup.
+ * While the cache is enabled by default, it can be switched off with the
+ * second parameter in the constructor (e.g. whenusing very large MO files
+ * that you don't want to keep in memory)
+ */
+class gettext_reader {
+ //public:
+ var $error = 0; // public variable that holds error code (0 if no error)
+
+ //private:
+ var $BYTEORDER = 0; // 0: low endian, 1: big endian
+ var $STREAM = NULL;
+ var $short_circuit = false;
+ var $enable_cache = false;
+ var $originals = NULL; // offset of original table
+ var $translations = NULL; // offset of translation table
+ var $pluralheader = NULL; // cache header field for plural forms
+ var $total = 0; // total string count
+ var $table_originals = NULL; // table for original strings (offsets)
+ var $table_translations = NULL; // table for translated strings (offsets)
+ var $cache_translations = NULL; // original -> translation mapping
+
+
+ /* Methods */
+
+
+ /**
+ * Reads a 32bit Integer from the Stream
+ *
+ * @access private
+ * @return Integer from the Stream
+ */
+ function readint() {
+ if ($this->BYTEORDER == 0) {
+ // low endian
+ $input=unpack('V', $this->STREAM->read(4));
+ return array_shift($input);
+ } else {
+ // big endian
+ $input=unpack('N', $this->STREAM->read(4));
+ return array_shift($input);
+ }
+ }
+
+ function read($bytes) {
+ return $this->STREAM->read($bytes);
+ }
+
+ /**
+ * Reads an array of Integers from the Stream
+ *
+ * @param int count How many elements should be read
+ * @return Array of Integers
+ */
+ function readintarray($count) {
+ if ($this->BYTEORDER == 0) {
+ // low endian
+ return unpack('V'.$count, $this->STREAM->read(4 * $count));
+ } else {
+ // big endian
+ return unpack('N'.$count, $this->STREAM->read(4 * $count));
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param object Reader the StreamReader object
+ * @param boolean enable_cache Enable or disable caching of strings (default on)
+ */
+ function gettext_reader($Reader, $enable_cache = true) {
+ // If there isn't a StreamReader, turn on short circuit mode.
+ if (! $Reader || isset($Reader->error) ) {
+ $this->short_circuit = true;
+ return;
+ }
+
+ // Caching can be turned off
+ $this->enable_cache = $enable_cache;
+
+ $MAGIC1 = "\x95\x04\x12\xde";
+ $MAGIC2 = "\xde\x12\x04\x95";
+
+ $this->STREAM = $Reader;
+ $magic = $this->read(4);
+ if ($magic == $MAGIC1) {
+ $this->BYTEORDER = 1;
+ } elseif ($magic == $MAGIC2) {
+ $this->BYTEORDER = 0;
+ } else {
+ $this->error = 1; // not MO file
+ return false;
+ }
+
+ // FIXME: Do we care about revision? We should.
+ $revision = $this->readint();
+
+ $this->total = $this->readint();
+ $this->originals = $this->readint();
+ $this->translations = $this->readint();
+ }
+
+ /**
+ * Loads the translation tables from the MO file into the cache
+ * If caching is enabled, also loads all strings into a cache
+ * to speed up translation lookups
+ *
+ * @access private
+ */
+ function load_tables() {
+ if (is_array($this->cache_translations) &&
+ is_array($this->table_originals) &&
+ is_array($this->table_translations))
+ return;
+
+ /* get original and translations tables */
+ if (!is_array($this->table_originals)) {
+ $this->STREAM->seekto($this->originals);
+ $this->table_originals = $this->readintarray($this->total * 2);
+ }
+ if (!is_array($this->table_translations)) {
+ $this->STREAM->seekto($this->translations);
+ $this->table_translations = $this->readintarray($this->total * 2);
+ }
+
+ if ($this->enable_cache) {
+ $this->cache_translations = array ();
+ /* read all strings in the cache */
+ for ($i = 0; $i < $this->total; $i++) {
+ $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
+ $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
+ $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
+ $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
+ $this->cache_translations[$original] = $translation;
+ }
+ }
+ }
+
+ /**
+ * Returns a string from the "originals" table
+ *
+ * @access private
+ * @param int num Offset number of original string
+ * @return string Requested string if found, otherwise ''
+ */
+ function get_original_string($num) {
+ $length = $this->table_originals[$num * 2 + 1];
+ $offset = $this->table_originals[$num * 2 + 2];
+ if (! $length)
+ return '';
+ $this->STREAM->seekto($offset);
+ $data = $this->STREAM->read($length);
+ return (string)$data;
+ }
+
+ /**
+ * Returns a string from the "translations" table
+ *
+ * @access private
+ * @param int num Offset number of original string
+ * @return string Requested string if found, otherwise ''
+ */
+ function get_translation_string($num) {
+ $length = $this->table_translations[$num * 2 + 1];
+ $offset = $this->table_translations[$num * 2 + 2];
+ if (! $length)
+ return '';
+ $this->STREAM->seekto($offset);
+ $data = $this->STREAM->read($length);
+ return (string)$data;
+ }
+
+ /**
+ * Binary search for string
+ *
+ * @access private
+ * @param string string
+ * @param int start (internally used in recursive function)
+ * @param int end (internally used in recursive function)
+ * @return int string number (offset in originals table)
+ */
+ function find_string($string, $start = -1, $end = -1) {
+ if (($start == -1) or ($end == -1)) {
+ // find_string is called with only one parameter, set start end end
+ $start = 0;
+ $end = $this->total;
+ }
+ if (abs($start - $end) <= 1) {
+ // We're done, now we either found the string, or it doesn't exist
+ $txt = $this->get_original_string($start);
+ if ($string == $txt)
+ return $start;
+ else
+ return -1;
+ } else if ($start > $end) {
+ // start > end -> turn around and start over
+ return $this->find_string($string, $end, $start);
+ } else {
+ // Divide table in two parts
+ $half = (int)(($start + $end) / 2);
+ $cmp = strcmp($string, $this->get_original_string($half));
+ if ($cmp == 0)
+ // string is exactly in the middle => return it
+ return $half;
+ else if ($cmp < 0)
+ // The string is in the upper half
+ return $this->find_string($string, $start, $half);
+ else
+ // The string is in the lower half
+ return $this->find_string($string, $half, $end);
+ }
+ }
+
+ /**
+ * Translates a string
+ *
+ * @access public
+ * @param string string to be translated
+ * @return string translated string (or original, if not found)
+ */
+ function translate($string) {
+ if ($this->short_circuit)
+ return $string;
+ $this->load_tables();
+
+ if ($this->enable_cache) {
+ // Caching enabled, get translated string from cache
+ if (array_key_exists($string, $this->cache_translations))
+ return $this->cache_translations[$string];
+ else
+ return $string;
+ } else {
+ // Caching not enabled, try to find string
+ $num = $this->find_string($string);
+ if ($num == -1)
+ return $string;
+ else
+ return $this->get_translation_string($num);
+ }
+ }
+
+ /**
+ * Sanitize plural form expression for use in PHP eval call.
+ *
+ * @access private
+ * @return string sanitized plural form expression
+ */
+ function sanitize_plural_expression($expr) {
+ // Get rid of disallowed characters.
+ $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
+
+ // Add parenthesis for tertiary '?' operator.
+ $expr .= ';';
+ $res = '';
+ $p = 0;
+ for ($i = 0; $i < strlen($expr); $i++) {
+ $ch = $expr[$i];
+ switch ($ch) {
+ case '?':
+ $res .= ' ? (';
+ $p++;
+ break;
+ case ':':
+ $res .= ') : (';
+ break;
+ case ';':
+ $res .= str_repeat( ')', $p) . ';';
+ $p = 0;
+ break;
+ default:
+ $res .= $ch;
+ }
+ }
+ return $res;
+ }
+
+ /**
+ * Parse full PO header and extract only plural forms line.
+ *
+ * @access private
+ * @return string verbatim plural form header field
+ */
+ function extract_plural_forms_header_from_po_header($header) {
+ if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs))
+ $expr = $regs[2];
+ else
+ $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
+ return $expr;
+ }
+
+ /**
+ * Get possible plural forms from MO header
+ *
+ * @access private
+ * @return string plural form header
+ */
+ function get_plural_forms() {
+ // lets assume message number 0 is header
+ // this is true, right?
+ $this->load_tables();
+
+ // cache header field for plural forms
+ if (! is_string($this->pluralheader)) {
+ if ($this->enable_cache) {
+ $header = $this->cache_translations[""];
+ } else {
+ $header = $this->get_translation_string(0);
+ }
+ $expr = $this->extract_plural_forms_header_from_po_header($header);
+ $this->pluralheader = $this->sanitize_plural_expression($expr);
+ }
+ return $this->pluralheader;
+ }
+
+ /**
+ * Detects which plural form to take
+ *
+ * @access private
+ * @param n count
+ * @return int array index of the right plural form
+ */
+ function select_string($n) {
+ $string = $this->get_plural_forms();
+ $string = str_replace('nplurals',"\$total",$string);
+ $string = str_replace("n",$n,$string);
+ $string = str_replace('plural',"\$plural",$string);
+
+ $total = 0;
+ $plural = 0;
+
+ eval("$string");
+ if ($plural >= $total) $plural = $total - 1;
+ return $plural;
+ }
+
+ /**
+ * Plural version of gettext
+ *
+ * @access public
+ * @param string single
+ * @param string plural
+ * @param string number
+ * @return translated plural form
+ */
+ function ngettext($single, $plural, $number) {
+ if ($this->short_circuit) {
+ if ($number != 1)
+ return $plural;
+ else
+ return $single;
+ }
+
+ // find out the appropriate form
+ $select = $this->select_string($number);
+
+ // this should contains all strings separated by NULLs
+ $key = $single . chr(0) . $plural;
+
+
+ if ($this->enable_cache) {
+ if (! array_key_exists($key, $this->cache_translations)) {
+ return ($number != 1) ? $plural : $single;
+ } else {
+ $result = $this->cache_translations[$key];
+ $list = explode(chr(0), $result);
+ return $list[$select];
+ }
+ } else {
+ $num = $this->find_string($key);
+ if ($num == -1) {
+ return ($number != 1) ? $plural : $single;
+ } else {
+ $result = $this->get_translation_string($num);
+ $list = explode(chr(0), $result);
+ return $list[$select];
+ }
+ }
+ }
+
+ function pgettext($context, $msgid) {
+ $key = $context . chr(4) . $msgid;
+ $ret = $this->translate($key);
+ if (strpos($ret, "\004") !== FALSE) {
+ return $msgid;
+ } else {
+ return $ret;
+ }
+ }
+
+ function npgettext($context, $singular, $plural, $number) {
+ $key = $context . chr(4) . $singular;
+ $ret = $this->ngettext($key, $plural, $number);
+ if (strpos($ret, "\004") !== FALSE) {
+ return $singular;
+ } else {
+ return $ret;
+ }
+
+ }
+}
+
+?>
diff --git a/simpleid/www/lib/gettext/streams.php b/simpleid/www/lib/gettext/streams.php
new file mode 100644
index 0000000..3cdc158
--- /dev/null
+++ b/simpleid/www/lib/gettext/streams.php
@@ -0,0 +1,167 @@
+<?php
+/*
+ Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+
+ // Simple class to wrap file streams, string streams, etc.
+ // seek is essential, and it should be byte stream
+class StreamReader {
+ // should return a string [FIXME: perhaps return array of bytes?]
+ function read($bytes) {
+ return false;
+ }
+
+ // should return new position
+ function seekto($position) {
+ return false;
+ }
+
+ // returns current position
+ function currentpos() {
+ return false;
+ }
+
+ // returns length of entire stream (limit for seekto()s)
+ function length() {
+ return false;
+ }
+};
+
+class StringReader {
+ var $_pos;
+ var $_str;
+
+ function StringReader($str='') {
+ $this->_str = $str;
+ $this->_pos = 0;
+ }
+
+ function read($bytes) {
+ $data = substr($this->_str, $this->_pos, $bytes);
+ $this->_pos += $bytes;
+ if (strlen($this->_str)<$this->_pos)
+ $this->_pos = strlen($this->_str);
+
+ return $data;
+ }
+
+ function seekto($pos) {
+ $this->_pos = $pos;
+ if (strlen($this->_str)<$this->_pos)
+ $this->_pos = strlen($this->_str);
+ return $this->_pos;
+ }
+
+ function currentpos() {
+ return $this->_pos;
+ }
+
+ function length() {
+ return strlen($this->_str);
+ }
+
+};
+
+
+class FileReader {
+ var $_pos;
+ var $_fd;
+ var $_length;
+
+ function FileReader($filename) {
+ if (file_exists($filename)) {
+
+ $this->_length=filesize($filename);
+ $this->_pos = 0;
+ $this->_fd = fopen($filename,'rb');
+ if (!$this->_fd) {
+ $this->error = 3; // Cannot read file, probably permissions
+ return false;
+ }
+ } else {
+ $this->error = 2; // File doesn't exist
+ return false;
+ }
+ }
+
+ function read($bytes) {
+ if ($bytes) {
+ fseek($this->_fd, $this->_pos);
+
+ // PHP 5.1.1 does not read more than 8192 bytes in one fread()
+ // the discussions at PHP Bugs suggest it's the intended behaviour
+ $data = '';
+ while ($bytes > 0) {
+ $chunk = fread($this->_fd, $bytes);
+ $data .= $chunk;
+ $bytes -= strlen($chunk);
+ }
+ $this->_pos = ftell($this->_fd);
+
+ return $data;
+ } else return '';
+ }
+
+ function seekto($pos) {
+ fseek($this->_fd, $pos);
+ $this->_pos = ftell($this->_fd);
+ return $this->_pos;
+ }
+
+ function currentpos() {
+ return $this->_pos;
+ }
+
+ function length() {
+ return $this->_length;
+ }
+
+ function close() {
+ fclose($this->_fd);
+ }
+
+};
+
+// Preloads entire file in memory first, then creates a StringReader
+// over it (it assumes knowledge of StringReader internals)
+class CachedFileReader extends StringReader {
+ function CachedFileReader($filename) {
+ if (file_exists($filename)) {
+
+ $length=filesize($filename);
+ $fd = fopen($filename,'rb');
+
+ if (!$fd) {
+ $this->error = 3; // Cannot read file, probably permissions
+ return false;
+ }
+ $this->_str = fread($fd, $length);
+ fclose($fd);
+
+ } else {
+ $this->error = 2; // File doesn't exist
+ return false;
+ }
+ }
+};
+
+
+?>
diff --git a/simpleid/www/lib/xtemplate.class.php b/simpleid/www/lib/xtemplate.class.php
new file mode 100644
index 0000000..84713c6
--- /dev/null
+++ b/simpleid/www/lib/xtemplate.class.php
@@ -0,0 +1,1301 @@
+<?php
+
+// When developing uncomment the line below, re-comment before making public
+//error_reporting(E_ALL);
+
+/**
+ * XTemplate PHP templating engine
+ *
+ * @package XTemplate
+ * @author Barnabas Debreceni [cranx@users.sourceforge.net]
+ * @copyright Barnabas Debreceni 2000-2001
+ * @author Jeremy Coates [cocomp@users.sourceforge.net]
+ * @copyright Jeremy Coates 2002-2007
+ * @see license.txt LGPL / BSD license
+ * @since PHP 5
+ * @link $HeadURL: https://xtpl.svn.sourceforge.net/svnroot/xtpl/trunk/xtemplate.class.php $
+ * @version $Id$
+ *
+ *
+ * XTemplate class - http://www.phpxtemplate.org/ (x)html / xml generation with templates - fast & easy
+ * Latest stable & Subversion versions available @ http://sourceforge.net/projects/xtpl/
+ * License: LGPL / BSD - see license.txt
+ * Changelog: see changelog.txt
+ */
+class XTemplate {
+
+ /**
+ * Properties
+ */
+
+ /**
+ * Raw contents of the template file
+ *
+ * @access public
+ * @var string
+ */
+ public $filecontents = '';
+
+ /**
+ * Unparsed blocks
+ *
+ * @access public
+ * @var array
+ */
+ public $blocks = array();
+
+ /**
+ * Parsed blocks
+ *
+ * @var unknown_type
+ */
+ public $parsed_blocks = array();
+
+ /**
+ * Preparsed blocks (for file includes)
+ *
+ * @access public
+ * @var array
+ */
+ public $preparsed_blocks = array();
+
+ /**
+ * Block parsing order for recursive parsing
+ * (Sometimes reverse :)
+ *
+ * @access public
+ * @var array
+ */
+ public $block_parse_order = array();
+
+ /**
+ * Store sub-block names
+ * (For fast resetting)
+ *
+ * @access public
+ * @var array
+ */
+ public $sub_blocks = array();
+
+ /**
+ * Variables array
+ *
+ * @access public
+ * @var array
+ */
+ public $vars = array();
+
+ /**
+ * File variables array
+ *
+ * @access public
+ * @var array
+ */
+ public $filevars = array();
+
+ /**
+ * Filevars' parent block
+ *
+ * @access public
+ * @var array
+ */
+ public $filevar_parent = array();
+
+ /**
+ * File caching during duration of script
+ * e.g. files only cached to speed {FILE "filename"} repeats
+ *
+ * @access public
+ * @var array
+ */
+ public $filecache = array();
+
+ /**
+ * Location of template files
+ *
+ * @access public
+ * @var string
+ */
+ public $tpldir = '';
+
+ /**
+ * Filenames lookup table
+ *
+ * @access public
+ * @var null
+ */
+ public $files = null;
+
+ /**
+ * Template filename
+ *
+ * @access public
+ * @var string
+ */
+ public $filename = '';
+
+ // moved to setup method so uses the tag_start & end_delims
+ /**
+ * RegEx for file includes
+ *
+ * "/\{FILE\s*\"([^\"]+)\"\s*\}/m";
+ *
+ * @access public
+ * @var string
+ */
+ public $file_delim = '';
+
+ /**
+ * RegEx for file include variable
+ *
+ * "/\{FILE\s*\{([A-Za-z0-9\._]+?)\}\s*\}/m";
+ *
+ * @access public
+ * @var string
+ */
+ public $filevar_delim = '';
+
+ /**
+ * RegEx for file includes with newlines
+ *
+ * "/^\s*\{FILE\s*\{([A-Za-z0-9\._]+?)\}\s*\}\s*\n/m";
+ *
+ * @access public
+ * @var string
+ */
+ public $filevar_delim_nl = '';
+
+ /**
+ * Template block start delimiter
+ *
+ * @access public
+ * @var string
+ */
+ public $block_start_delim = '<!-- ';
+
+ /**
+ * Template block end delimiter
+ *
+ * @access public
+ * @var string
+ */
+ public $block_end_delim = '-->';
+
+ /**
+ * Template block start word
+ *
+ * @access public
+ * @var string
+ */
+ public $block_start_word = 'BEGIN:';
+
+ /**
+ * Template block end word
+ *
+ * The last 3 properties and this make the delimiters look like:
+ * @example <!-- BEGIN: block_name -->
+ * if you use the default syntax.
+ *
+ * @access public
+ * @var string
+ */
+ public $block_end_word = 'END:';
+
+ /**
+ * Template tag start delimiter
+ *
+ * This makes the delimiters look like:
+ * @example {tagname}
+ * if you use the default syntax.
+ *
+ * @access public
+ * @var string
+ */
+ public $tag_start_delim = '{';
+
+ /**
+ * Template tag end delimiter
+ *
+ * This makes the delimiters look like:
+ * @example {tagname}
+ * if you use the default syntax.
+ *
+ * @access public
+ * @var string
+ */
+ public $tag_end_delim = '}';
+ /* this makes the delimiters look like: {tagname} if you use my syntax. */
+
+ /**
+ * Regular expression element for comments within tags and blocks
+ *
+ * @example {tagname#My Comment}
+ * @example {tagname #My Comment}
+ * @example <!-- BEGIN: blockname#My Comment -->
+ * @example <!-- BEGIN: blockname #My Comment -->
+ *
+ * @access public
+ * @var string
+ */
+ public $comment_preg = '( ?#.*?)?';
+
+ /**
+ * Default main template block name
+ *
+ * @access public
+ * @var string
+ */
+ public $mainblock = 'main';
+
+ /**
+ * Script output type
+ *
+ * @access public
+ * @var string
+ */
+ public $output_type = 'HTML';
+
+ /**
+ * Debug mode
+ *
+ * @access public
+ * @var boolean
+ */
+ public $debug = false;
+
+ /**
+ * Null string for unassigned vars
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_null_string = array('' => '');
+
+ /**
+ * Null string for unassigned blocks
+ *
+ * @access protected
+ * @var array
+ */
+ protected $_null_block = array('' => '');
+
+ /**
+ * Errors
+ *
+ * @access protected
+ * @var string
+ */
+ protected $_error = '';
+
+ /**
+ * Auto-reset sub blocks
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $_autoreset = true;
+
+ /**
+ * Set to FALSE to generate errors if a non-existant blocks is referenced
+ *
+ * @author NW
+ * @since 2002/10/17
+ * @access protected
+ * @var boolean
+ */
+ protected $_ignore_missing_blocks = true;
+
+ /**
+ * PHP 5 Constructor - Instantiate the object
+ *
+ * @param string $file Template file to work on
+ * @param string/array $tpldir Location of template files (useful for keeping files outside web server root)
+ * @param array $files Filenames lookup
+ * @param string $mainblock Name of main block in the template
+ * @param boolean $autosetup If true, run setup() as part of constuctor
+ * @return XTemplate
+ */
+ public function __construct($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) {
+
+ $this->restart($file, $tpldir, $files, $mainblock, $autosetup, $this->tag_start_delim, $this->tag_end_delim);
+ }
+
+ /*
+ * PHP 4 Constructor - Instantiate the object
+ *
+ * @deprecated Use PHP 5 constructor instead
+ * @param string $file Template file to work on
+ * @param string/array $tpldir Location of template files (useful for keeping files outside web server root)
+ * @param array $files Filenames lookup
+ * @param string $mainblock Name of main block in the template
+ * @param boolean $autosetup If true, run setup() as part of constuctor
+ * @return XTemplate
+ *
+ public function XTemplate ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) {
+
+ assert('Deprecated - use PHP 5 constructor');
+ }*/
+
+
+ /***************************************************************************/
+ /***[ public stuff ]********************************************************/
+ /***************************************************************************/
+
+ /**
+ * Restart the class - allows one instantiation with several files processed by restarting
+ * e.g. $xtpl = new XTemplate('file1.xtpl');
+ * $xtpl->parse('main');
+ * $xtpl->out('main');
+ * $xtpl->restart('file2.xtpl');
+ * $xtpl->parse('main');
+ * $xtpl->out('main');
+ * (Added in response to sf:641407 feature request)
+ *
+ * @param string $file Template file to work on
+ * @param string/array $tpldir Location of template files
+ * @param array $files Filenames lookup
+ * @param string $mainblock Name of main block in the template
+ * @param boolean $autosetup If true, run setup() as part of restarting
+ * @param string $tag_start {
+ * @param string $tag_end }
+ */
+ public function restart ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') {
+
+ $this->filename = $file;
+
+ // From SF Feature request 1202027
+ // Kenneth Kalmer
+ $this->tpldir = $tpldir;
+ if (defined('XTPL_DIR') && empty($this->tpldir)) {
+ $this->tpldir = XTPL_DIR;
+ }
+
+ if (is_array($files)) {
+ $this->files = $files;
+ }
+
+ $this->mainblock = $mainblock;
+
+ $this->tag_start_delim = $tag_start;
+ $this->tag_end_delim = $tag_end;
+
+ // Start with fresh file contents
+ $this->filecontents = '';
+
+ // Reset the template arrays
+ $this->blocks = array();
+ $this->parsed_blocks = array();
+ $this->preparsed_blocks = array();
+ $this->block_parse_order = array();
+ $this->sub_blocks = array();
+ $this->vars = array();
+ $this->filevars = array();
+ $this->filevar_parent = array();
+ $this->filecache = array();
+
+ if ($autosetup) {
+ $this->setup();
+ }
+ }
+
+ /**
+ * setup - the elements that were previously in the constructor
+ *
+ * @access public
+ * @param boolean $add_outer If true is passed when called, it adds an outer main block to the file
+ */
+ public function setup ($add_outer = false) {
+
+ $this->tag_start_delim = preg_quote($this->tag_start_delim);
+ $this->tag_end_delim = preg_quote($this->tag_end_delim);
+
+ // Setup the file delimiters
+
+ // regexp for file includes
+ $this->file_delim = "/" . $this->tag_start_delim . "FILE\s*\"([^\"]+)\"" . $this->comment_preg . $this->tag_end_delim . "/m";
+
+ // regexp for file includes
+ $this->filevar_delim = "/" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "/m";
+
+ // regexp for file includes w/ newlines
+ $this->filevar_delim_nl = "/^\s*" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "\s*\n/m";
+
+ if (empty($this->filecontents)) {
+ // read in template file
+ $this->filecontents = $this->_r_getfile($this->filename);
+ }
+
+ if ($add_outer) {
+ $this->_add_outer_block();
+ }
+
+ // preprocess some stuff
+ $this->blocks = $this->_maketree($this->filecontents, '');
+ $this->filevar_parent = $this->_store_filevar_parents($this->blocks);
+ $this->scan_globals();
+ }
+
+ /**
+ * assign a variable
+ *
+ * @example Simplest case:
+ * @example $xtpl->assign('name', 'value');
+ * @example {name} in template
+ *
+ * @example Array assign:
+ * @example $xtpl->assign(array('name' => 'value', 'name2' => 'value2'));
+ * @example {name} {name2} in template
+ *
+ * @example Value as array assign:
+ * @example $xtpl->assign('name', array('key' => 'value', 'key2' => 'value2'));
+ * @example {name.key} {name.key2} in template
+ *
+ * @example Reset array:
+ * @example $xtpl->assign('name', array('key' => 'value', 'key2' => 'value2'));
+ * @example // Other code then:
+ * @example $xtpl->assign('name', array('key3' => 'value3'), false);
+ * @example {name.key} {name.key2} {name.key3} in template
+ *
+ * @access public
+ * @param string $name Variable to assign $val to
+ * @param string / array $val Value to assign to $name
+ * @param boolean $reset_array Reset the variable array if $val is an array
+ */
+ public function assign ($name, $val = '', $reset_array = true) {
+
+ if (is_array($name)) {
+
+ foreach ($name as $k => $v) {
+
+ $this->vars[$k] = $v;
+ }
+ } elseif (is_array($val)) {
+
+ // Clear the existing values
+ if ($reset_array) {
+ $this->vars[$name] = array();
+ }
+
+ foreach ($val as $k => $v) {
+
+ $this->vars[$name][$k] = $v;
+ }
+
+ } else {
+
+ $this->vars[$name] = $val;
+ }
+ }
+
+ /**
+ * assign a file variable
+ *
+ * @access public
+ * @param string $name Variable to assign $val to
+ * @param string / array $val Values to assign to $name
+ */
+ public function assign_file ($name, $val = '') {
+
+ if (is_array($name)) {
+
+ foreach ($name as $k => $v) {
+
+ $this->_assign_file_sub($k, $v);
+ }
+ } else {
+
+ $this->_assign_file_sub($name, $val);
+ }
+ }
+
+ /**
+ * parse a block
+ *
+ * @access public
+ * @param string $bname Block name to parse
+ */
+ public function parse ($bname) {
+
+ if (isset($this->preparsed_blocks[$bname])) {
+
+ $copy = $this->preparsed_blocks[$bname];
+
+ } elseif (isset($this->blocks[$bname])) {
+
+ $copy = $this->blocks[$bname];
+
+ } elseif ($this->_ignore_missing_blocks) {
+ // ------------------------------------------------------
+ // NW : 17 Oct 2002. Added default of ignore_missing_blocks
+ // to allow for generalised processing where some
+ // blocks may be removed from the HTML without the
+ // processing code needing to be altered.
+ // ------------------------------------------------------
+ // JRC: 3/1/2003 added set error to ignore missing functionality
+ $this->_set_error("parse: blockname [$bname] does not exist");
+ return;
+
+ } else {
+
+ $this->_set_error("parse: blockname [$bname] does not exist");
+ }
+
+ /* from there we should have no more {FILE } directives */
+ if (!isset($copy)) {
+ die('Block: ' . $bname);
+ }
+
+ $copy = preg_replace($this->filevar_delim_nl, '', $copy);
+
+ $var_array = array();
+
+ /* find & replace variables+blocks */
+ preg_match_all("|" . $this->tag_start_delim . "([A-Za-z0-9\._]+?" . $this->comment_preg . ")" . $this->tag_end_delim. "|", $copy, $var_array);
+
+ $var_array = $var_array[1];
+
+ foreach ($var_array as $k => $v) {
+
+ // Are there any comments in the tags {tag#a comment for documenting the template}
+ $any_comments = explode('#', $v);
+ $v = rtrim($any_comments[0]);
+
+ if (sizeof($any_comments) > 1) {
+
+ $comments = $any_comments[1];
+ } else {
+
+ $comments = '';
+ }
+
+ $sub = explode('.', $v);
+
+ if ($sub[0] == '_BLOCK_') {
+
+ unset($sub[0]);
+
+ $bname2 = implode('.', $sub);
+
+ // trinary operator eliminates assign error in E_ALL reporting
+ $var = isset($this->parsed_blocks[$bname2]) ? $this->parsed_blocks[$bname2] : null;
+ $nul = (!isset($this->_null_block[$bname2])) ? $this->_null_block[''] : $this->_null_block[$bname2];
+
+ if ($var === '') {
+
+ if ($nul == '') {
+ // -----------------------------------------------------------
+ // Removed requirement for blocks to be at the start of string
+ // -----------------------------------------------------------
+ // $copy=preg_replace("/^\s*\{".$v."\}\s*\n*/m","",$copy);
+ // Now blocks don't need to be at the beginning of a line,
+ //$copy=preg_replace("/\s*" . $this->tag_start_delim . $v . $this->tag_end_delim . "\s*\n*/m","",$copy);
+ $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", '', $copy);
+
+ } else {
+
+ $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", "$nul", $copy);
+ }
+ } else {
+
+ //$var = trim($var);
+ switch (true) {
+ case preg_match('/^\n/', $var) && preg_match('/\n$/', $var):
+ $var = substr($var, 1, -1);
+ break;
+
+ case preg_match('/^\n/', $var):
+ $var = substr($var, 1);
+ break;
+
+ case preg_match('/\n$/', $var):
+ $var = substr($var, 0, -1);
+ break;
+ }
+
+ // SF Bug no. 810773 - thanks anonymous
+ $var = str_replace('\\', '\\\\', $var);
+ // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04
+ $var = str_replace('$', '\\$', $var);
+ // Replaced str_replaces with preg_quote
+ //$var = preg_quote($var);
+ $var = str_replace('\\|', '|', $var);
+ $copy = preg_replace("|" . $this->tag_start_delim . $v . $this->tag_end_delim . "|m", "$var", $copy);
+
+ if (preg_match('/^\n/', $copy) && preg_match('/\n$/', $copy)) {
+ $copy = substr($copy, 1, -1);
+ }
+ }
+ } else {
+
+ $var = $this->vars;
+
+ foreach ($sub as $v1) {
+
+ // NW 4 Oct 2002 - Added isset and is_array check to avoid NOTICE messages
+ // JC 17 Oct 2002 - Changed EMPTY to stlen=0
+ // if (empty($var[$v1])) { // this line would think that zeros(0) were empty - which is not true
+ if (!isset($var[$v1]) || (!is_array($var[$v1]) && strlen($var[$v1]) == 0)) {
+
+ // Check for constant, when variable not assigned
+ if (defined($v1)) {
+
+ $var[$v1] = constant($v1);
+
+ } else {
+
+ $var[$v1] = null;
+ }
+ }
+
+ $var = $var[$v1];
+ }
+
+ $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]);
+ $var = (!isset($var)) ? $nul : $var;
+
+ if ($var === '') {
+ // -----------------------------------------------------------
+ // Removed requriement for blocks to be at the start of string
+ // -----------------------------------------------------------
+ // $copy=preg_replace("|^\s*\{".$v." ?#?".$comments."\}\s*\n|m","",$copy);
+ $copy = preg_replace("|" . $this->tag_start_delim . $v . "( ?#" . $comments . ")?" . $this->tag_end_delim . "|m", '', $copy);
+ }
+
+ $var = trim($var);
+ // SF Bug no. 810773 - thanks anonymous
+ $var = str_replace('\\', '\\\\', $var);
+ // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04
+ $var = str_replace('$', '\\$', $var);
+ // Replace str_replaces with preg_quote
+ //$var = preg_quote($var);
+ $var = str_replace('\\|', '|', $var);
+ $copy = preg_replace("|" . $this->tag_start_delim . $v . "( ?#" . $comments . ")?" . $this->tag_end_delim . "|m", "$var", $copy);
+
+ if (preg_match('/^\n/', $copy) && preg_match('/\n$/', $copy)) {
+ $copy = substr($copy, 1);
+ }
+ }
+ }
+
+ if (isset($this->parsed_blocks[$bname])) {
+ $this->parsed_blocks[$bname] .= $copy;
+ } else {
+ $this->parsed_blocks[$bname] = $copy;
+ }
+
+ /* reset sub-blocks */
+ if ($this->_autoreset && (!empty($this->sub_blocks[$bname]))) {
+
+ reset($this->sub_blocks[$bname]);
+
+ foreach ($this->sub_blocks[$bname] as $k => $v) {
+ $this->reset($v);
+ }
+ }
+ }
+
+ /**
+ * returns the parsed text for a block, including all sub-blocks.
+ *
+ * @access public
+ * @param string $bname Block name to parse
+ */
+ public function rparse ($bname) {
+
+ if (!empty($this->sub_blocks[$bname])) {
+
+ reset($this->sub_blocks[$bname]);
+
+ foreach ($this->sub_blocks[$bname] as $k => $v) {
+
+ if (!empty($v)) {
+ $this->rparse($v);
+ }
+ }
+ }
+
+ $this->parse($bname);
+ }
+
+ /**
+ * inserts a loop ( call assign & parse )
+ *
+ * @access public
+ * @param string $bname Block name to assign
+ * @param string $var Variable to assign values to
+ * @param string / array $value Value to assign to $var
+ */
+ public function insert_loop ($bname, $var, $value = '') {
+
+ $this->assign($var, $value);
+ $this->parse($bname);
+ }
+
+ /**
+ * parses a block for every set of data in the values array
+ *
+ * @access public
+ * @param string $bname Block name to loop
+ * @param string $var Variable to assign values to
+ * @param array $values Values to assign to $var
+ */
+ public function array_loop ($bname, $var, &$values) {
+
+ if (is_array($values)) {
+
+ foreach($values as $v) {
+
+ $this->insert_loop($bname, $var, $v);
+ }
+ }
+ }
+
+ /**
+ * returns the parsed text for a block
+ *
+ * @access public
+ * @param string $bname Block name to return
+ * @return string
+ */
+ public function text ($bname = '') {
+
+ $text = '';
+
+ if ($this->debug && $this->output_type == 'HTML') {
+ // JC 20/11/02 echo the template filename if in development as
+ // html comment
+ $text .= '<!-- XTemplate: ' . realpath($this->filename) . " -->\n";
+ }
+
+ $bname = !empty($bname) ? $bname : $this->mainblock;
+
+ $text .= isset($this->parsed_blocks[$bname]) ? $this->parsed_blocks[$bname] : $this->get_error();
+
+ return $text;
+ }
+
+ /**
+ * prints the parsed text
+ *
+ * @access public
+ * @param string $bname Block name to echo out
+ */
+ public function out ($bname) {
+
+ $out = $this->text($bname);
+ // $length=strlen($out);
+ //header("Content-Length: ".$length); // TODO: Comment this back in later
+
+ echo $out;
+ }
+
+ /**
+ * prints the parsed text to a specified file
+ *
+ * @access public
+ * @param string $bname Block name to write out
+ * @param string $fname File name to write to
+ */
+ public function out_file ($bname, $fname) {
+
+ if (!empty($bname) && !empty($fname) && is_writeable($fname)) {
+
+ $fp = fopen($fname, 'w');
+ fwrite($fp, $this->text($bname));
+ fclose($fp);
+ }
+ }
+
+ /**
+ * resets the parsed text
+ *
+ * @access public
+ * @param string $bname Block to reset
+ */
+ public function reset ($bname) {
+
+ $this->parsed_blocks[$bname] = '';
+ }
+
+ /**
+ * returns true if block was parsed, false if not
+ *
+ * @access public
+ * @param string $bname Block name to test
+ * @return boolean
+ */
+ public function parsed ($bname) {
+
+ return (!empty($this->parsed_blocks[$bname]));
+ }
+
+ /**
+ * sets the string to replace in case the var was not assigned
+ *
+ * @access public
+ * @param string $str Display string for null block
+ * @param string $varname Variable name to apply $str to
+ */
+ public function set_null_string($str, $varname = '') {
+
+ $this->_null_string[$varname] = $str;
+ }
+
+ /**
+ * Backwards compatibility only
+ *
+ * @param string $str
+ * @param string $varname
+ * @deprecated Change to set_null_string to keep in with rest of naming convention
+ */
+ public function SetNullString ($str, $varname = '') {
+ $this->set_null_string($str, $varname);
+ }
+
+ /**
+ * sets the string to replace in case the block was not parsed
+ *
+ * @access public
+ * @param string $str Display string for null block
+ * @param string $bname Block name to apply $str to
+ */
+ public function set_null_block ($str, $bname = '') {
+
+ $this->_null_block[$bname] = $str;
+ }
+
+ /**
+ * Backwards compatibility only
+ *
+ * @param string $str
+ * @param string $bname
+ * @deprecated Change to set_null_block to keep in with rest of naming convention
+ */
+ public function SetNullBlock ($str, $bname = '') {
+ $this->set_null_block($str, $bname);
+ }
+
+ /**
+ * sets AUTORESET to 1. (default is 1)
+ * if set to 1, parse() automatically resets the parsed blocks' sub blocks
+ * (for multiple level blocks)
+ *
+ * @access public
+ */
+ public function set_autoreset () {
+
+ $this->_autoreset = true;
+ }
+
+ /**
+ * sets AUTORESET to 0. (default is 1)
+ * if set to 1, parse() automatically resets the parsed blocks' sub blocks
+ * (for multiple level blocks)
+ *
+ * @access public
+ */
+ public function clear_autoreset () {
+
+ $this->_autoreset = false;
+ }
+
+ /**
+ * scans global variables and assigns to PHP array
+ *
+ * @access public
+ */
+ public function scan_globals () {
+
+ foreach ($GLOBALS as $k => $v) {
+ $GLOB[$k] = $v;
+ }
+
+ /**
+ * Access global variables as:
+ * @example {PHP._SERVER.HTTP_HOST}
+ * in your template!
+ */
+ $this->assign('PHP', $GLOB);
+ }
+
+ /**
+ * gets error condition / string
+ *
+ * @access public
+ * @return boolean / string
+ */
+ public function get_error () {
+
+ // JRC: 3/1/2003 Added ouptut wrapper and detection of output type for error message output
+ $retval = false;
+
+ if ($this->_error != '') {
+
+ switch ($this->output_type) {
+ case 'HTML':
+ case 'html':
+ $retval = '<b>[XTemplate]</b><ul>' . nl2br(str_replace('* ', '<li>', str_replace(" *\n", "</li>\n", $this->_error))) . '</ul>';
+ break;
+
+ default:
+ $retval = '[XTemplate] ' . str_replace(' *\n', "\n", $this->_error);
+ break;
+ }
+ }
+
+ return $retval;
+ }
+
+ /***************************************************************************/
+ /***[ private stuff ]*******************************************************/
+ /***************************************************************************/
+
+ /**
+ * generates the array containing to-be-parsed stuff:
+ * $blocks["main"],$blocks["main.table"],$blocks["main.table.row"], etc.
+ * also builds the reverse parse order.
+ *
+ * @access public - aiming for private
+ * @param string $con content to be processed
+ * @param string $parentblock name of the parent block in the block hierarchy
+ */
+ public function _maketree ($con, $parentblock='') {
+
+ $blocks = array();
+
+ $con2 = explode($this->block_start_delim, $con);
+
+ if (!empty($parentblock)) {
+
+ $block_names = explode('.', $parentblock);
+ $level = sizeof($block_names);
+
+ } else {
+
+ $block_names = array();
+ $level = 0;
+ }
+
+ // JRC 06/04/2005 Added block comments (on BEGIN or END) <!-- BEGIN: block_name#Comments placed here -->
+ //$patt = "($this->block_start_word|$this->block_end_word)\s*(\w+)\s*$this->block_end_delim(.*)";
+ $patt = "(" . $this->block_start_word . "|" . $this->block_end_word . ")\s*(\w+)" . $this->comment_preg . "\s*" . $this->block_end_delim . "(.*)";
+
+ foreach($con2 as $k => $v) {
+
+ $res = array();
+
+ if (preg_match_all("/$patt/ims", $v, $res, PREG_SET_ORDER)) {
+ // $res[0][1] = BEGIN or END
+ // $res[0][2] = block name
+ // $res[0][3] = comment
+ // $res[0][4] = kinda content
+ $block_word = $res[0][1];
+ $block_name = $res[0][2];
+ $comment = $res[0][3];
+ $content = $res[0][4];
+
+ if (strtoupper($block_word) == $this->block_start_word) {
+
+ $parent_name = implode('.', $block_names);
+
+ // add one level - array("main","table","row")
+ $block_names[++$level] = $block_name;
+
+ // make block name (main.table.row)
+ $cur_block_name=implode('.', $block_names);
+
+ // build block parsing order (reverse)
+ $this->block_parse_order[] = $cur_block_name;
+
+ //add contents. trinary operator eliminates assign error in E_ALL reporting
+ $blocks[$cur_block_name] = isset($blocks[$cur_block_name]) ? $blocks[$cur_block_name] . $content : $content;
+
+ // add {_BLOCK_.blockname} string to parent block
+ $blocks[$parent_name] .= str_replace('\\', '', $this->tag_start_delim) . '_BLOCK_.' . $cur_block_name . str_replace('\\', '', $this->tag_end_delim);
+
+ // store sub block names for autoresetting and recursive parsing
+ $this->sub_blocks[$parent_name][] = $cur_block_name;
+
+ // store sub block names for autoresetting
+ $this->sub_blocks[$cur_block_name][] = '';
+
+ } else if (strtoupper($block_word) == $this->block_end_word) {
+
+ unset($block_names[$level--]);
+
+ $parent_name = implode('.', $block_names);
+
+ // add rest of block to parent block
+ $blocks[$parent_name] .= $content;
+ }
+ } else {
+
+ // no block delimiters found
+ // Saves doing multiple implodes - less overhead
+ $tmp = implode('.', $block_names);
+
+ if ($k) {
+ $blocks[$tmp] .= $this->block_start_delim;
+ }
+
+ // trinary operator eliminates assign error in E_ALL reporting
+ $blocks[$tmp] = isset($blocks[$tmp]) ? $blocks[$tmp] . $v : $v;
+ }
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Sub processing for assign_file method
+ *
+ * @access private
+ * @param string $name
+ * @param string $val
+ */
+ private function _assign_file_sub ($name, $val) {
+
+ if (isset($this->filevar_parent[$name])) {
+
+ if ($val != '') {
+
+ $val = $this->_r_getfile($val);
+
+ foreach($this->filevar_parent[$name] as $parent) {
+
+ if (isset($this->preparsed_blocks[$parent]) && !isset($this->filevars[$name])) {
+
+ $copy = $this->preparsed_blocks[$parent];
+
+ } elseif (isset($this->blocks[$parent])) {
+
+ $copy = $this->blocks[$parent];
+ }
+
+ $res = array();
+
+ preg_match_all($this->filevar_delim, $copy, $res, PREG_SET_ORDER);
+
+ if (is_array($res) && isset($res[0])) {
+
+ // Changed as per solution in SF bug ID #1261828
+ foreach ($res as $v) {
+
+ // Changed as per solution in SF bug ID #1261828
+ if ($v[1] == $name) {
+
+ // Changed as per solution in SF bug ID #1261828
+ $copy = preg_replace("/" . preg_quote($v[0]) . "/", "$val", $copy);
+ $this->preparsed_blocks = array_merge($this->preparsed_blocks, $this->_maketree($copy, $parent));
+ $this->filevar_parent = array_merge($this->filevar_parent, $this->_store_filevar_parents($this->preparsed_blocks));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $this->filevars[$name] = $val;
+ }
+
+ /**
+ * store container block's name for file variables
+ *
+ * @access public - aiming for private
+ * @param array $blocks
+ * @return array
+ */
+ public function _store_filevar_parents ($blocks){
+
+ $parents = array();
+
+ foreach ($blocks as $bname => $con) {
+
+ $res = array();
+
+ preg_match_all($this->filevar_delim, $con, $res);
+
+ foreach ($res[1] as $k => $v) {
+
+ $parents[$v][] = $bname;
+ }
+ }
+ return $parents;
+ }
+
+ /**
+ * Set the error string
+ *
+ * @access private
+ * @param string $str
+ */
+ private function _set_error ($str) {
+
+ // JRC: 3/1/2003 Made to append the error messages
+ $this->_error .= '* ' . $str . " *\n";
+ // JRC: 3/1/2003 Removed trigger error, use this externally if you want it eg. trigger_error($xtpl->get_error())
+ //trigger_error($this->get_error());
+ }
+
+ /**
+ * returns the contents of a file
+ *
+ * @access protected
+ * @param string $file
+ * @return string
+ */
+ protected function _getfile ($file) {
+
+ if (!isset($file)) {
+ // JC 19/12/02 added $file to error message
+ $this->_set_error('!isset file name!' . $file);
+
+ return '';
+ }
+
+ // check if filename is mapped to other filename
+ if (isset($this->files)) {
+
+ if (isset($this->files[$file])) {
+
+ $file = $this->files[$file];
+ }
+ }
+
+ // prepend template dir
+ if (!empty($this->tpldir)) {
+
+ /**
+ * Support hierarchy of file locations to search
+ *
+ * @example Supply array of filepaths when instantiating
+ * First path supplied that has the named file is prioritised
+ * $xtpl = new XTemplate('myfile.xtpl', array('.','/mypath', '/mypath2'));
+ * @since 29/05/2007
+ */
+ if (is_array($this->tpldir)) {
+
+ foreach ($this->tpldir as $dir) {
+
+ if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
+ $file = $dir . DIRECTORY_SEPARATOR . $file;
+ break;
+ }
+ }
+ } else {
+
+ $file = $this->tpldir. DIRECTORY_SEPARATOR . $file;
+ }
+ }
+
+ $file_text = '';
+
+ if (isset($this->filecache[$file])) {
+
+ $file_text .= $this->filecache[$file];
+
+ if ($this->debug) {
+ $file_text = '<!-- XTemplate debug cached: ' . realpath($file) . ' -->' . "\n" . $file_text;
+ }
+
+ } else {
+
+ if (is_file($file) && is_readable($file)) {
+
+ if (filesize($file)) {
+
+ if (!($fh = fopen($file, 'r'))) {
+
+ $this->_set_error('Cannot open file: ' . realpath($file));
+ return '';
+ }
+
+ $file_text .= fread($fh,filesize($file));
+ fclose($fh);
+
+ }
+
+ if ($this->debug) {
+ $file_text = '<!-- XTemplate debug: ' . realpath($file) . ' -->' . "\n" . $file_text;
+ }
+
+ } elseif (str_replace('.', '', phpversion()) >= '430' && $file_text = @file_get_contents($file, true)) {
+ // Enable use of include path by using file_get_contents
+ // Implemented at suggestion of SF Feature Request ID #1529478 michaelgroh
+ if ($file_text === false) {
+ $this->_set_error("[" . realpath($file) . "] ($file) does not exist");
+ $file_text = "<b>__XTemplate fatal error: file [$file] does not exist in the include path__</b>";
+ } elseif ($this->debug) {
+ $file_text = '<!-- XTemplate debug: ' . realpath($file) . ' (via include path) -->' . "\n" . $file_text;
+ }
+ } elseif (!is_file($file)) {
+
+ // NW 17 Oct 2002 : Added realpath around the file name to identify where the code is searching.
+ $this->_set_error("[" . realpath($file) . "] ($file) does not exist");
+ $file_text .= "<b>__XTemplate fatal error: file [$file] does not exist__</b>";
+
+ } elseif (!is_readable($file)) {
+
+ $this->_set_error("[" . realpath($file) . "] ($file) is not readable");
+ $file_text .= "<b>__XTemplate fatal error: file [$file] is not readable__</b>";
+ }
+
+ $this->filecache[$file] = $file_text;
+ }
+
+ return $file_text;
+ }
+
+ /**
+ * recursively gets the content of a file with {FILE "filename.tpl"} directives
+ *
+ * @access public - aiming for private
+ * @param string $file
+ * @return string
+ */
+ public function _r_getfile ($file) {
+
+ $text = $this->_getfile($file);
+
+ $res = array();
+
+ while (preg_match($this->file_delim,$text,$res)) {
+
+ $text2 = $this->_getfile($res[1]);
+ $text = preg_replace("'".preg_quote($res[0])."'",$text2,$text);
+ }
+
+ return $text;
+ }
+
+
+ /**
+ * add an outer block delimiter set useful for rtfs etc - keeps them editable in word
+ *
+ * @access private
+ */
+ private function _add_outer_block () {
+
+ $before = $this->block_start_delim . $this->block_start_word . ' ' . $this->mainblock . ' ' . $this->block_end_delim;
+ $after = $this->block_start_delim . $this->block_end_word . ' ' . $this->mainblock . ' ' . $this->block_end_delim;
+
+ $this->filecontents = $before . "\n" . $this->filecontents . "\n" . $after;
+ }
+
+ /**
+ * Debug function - var_dump wrapped in '<pre></pre>' tags
+ *
+ * @access private
+ * @param multiple var_dumps all the supplied arguments
+ */
+ private function _pre_var_dump ($args) {
+
+ if ($this->debug) {
+ echo '<pre>';
+ var_dump(func_get_args());
+ echo '</pre>';
+ }
+ }
+} /* end of XTemplate class. */
+
+?>
diff --git a/simpleid/www/locale.inc.php b/simpleid/www/locale.inc.php
new file mode 100644
index 0000000..837dde7
--- /dev/null
+++ b/simpleid/www/locale.inc.php
@@ -0,0 +1,77 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2012
+ *
+ * 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$
+ */
+/**
+ * Localisation support.
+ *
+ * @package simpleid
+ * @since 0.9
+ * @filesource
+ */
+
+include_once 'lib/gettext/gettext.inc.php';
+
+/**
+ * Initialises the localisation system.
+ *
+ * @param string $locale the locale to use
+ */
+function locale_init($locale) {
+ T_setlocale(LC_MESSAGES, $locale);
+ // Set the text domain as 'messages'
+ $domain = 'messages';
+ bindtextdomain($domain, 'locale');
+ // bind_textdomain_codeset is supported only in PHP 4.2.0+
+ if (function_exists('bind_textdomain_codeset'))
+ bind_textdomain_codeset($domain, 'UTF-8');
+ textdomain($domain);
+}
+
+/**
+ * Translates a string.
+ *
+ * @param string $string the string to translate
+ * @param array $variables an array of replacements variables to be made after
+ * a translation. Prefix the variable with a @ to make the replacement HTML safe,
+ * a % to make the replacement HTML safe and surround with <strong> tags,
+ * and ! to replace as is
+ * @return string the translated string
+ */
+function t($string, $variables = array()) {
+ $translated = gettext($string);
+
+ foreach ($variables as $variable => $value) {
+ switch ($variable[0]) {
+ case '@':
+ $variables[$variable] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
+ break;
+ case '%':
+ default:
+ $variables[$variable] = '<strong>' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '</strong>';
+ break;
+ case '!':
+ // Pass-through.
+ }
+ }
+ return strtr($translated, $variables);
+}
+?>
diff --git a/simpleid/www/locale/messages.pot b/simpleid/www/locale/messages.pot
new file mode 100644
index 0000000..1eff63d
--- /dev/null
+++ b/simpleid/www/locale/messages.pot
@@ -0,0 +1,550 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-03-13 21:46+1100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: www/common.inc.php:140
+msgid "An encrypted connection (HTTPS) is required for this page."
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:195
+msgid "SimpleID will also be sending the following information to the site."
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:196
+#: www/extensions/ax/ax.extension.php:267
+msgid "Type URL"
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:197
+#: www/extensions/ax/ax.extension.php:268
+#: www/extensions/sreg/sreg.extension.php:134
+#: www/extensions/sreg/sreg.extension.php:186 www/user.inc.php:781
+msgid "Value"
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:203
+msgid "This web site requested to store information about you on SimpleID. Sadly, SimpleID does not support this feature."
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:265
+msgid "SimpleID will send the following information to sites which supports the Attribute Exchange Extension. If you have also supplied OpenID Connect user information in your identity, or have the Simple Registration Extension installed, these may also be sent as part of this Extension."
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:266
+#: www/extensions/sreg/sreg.extension.php:184 www/user.inc.php:779
+msgid "To change these, <a href=\"!url\">edit your identity file</a>."
+msgstr ""
+
+#: www/extensions/ax/ax.extension.php:274
+msgid "Attribute Exchange Extension"
+msgstr ""
+
+#: www/extensions/pape/pape.extension.php:81
+msgid "This web site's policy requires you to log in again to confirm your identity."
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:114
+msgid "You can view the site's policy in relation to the use of this information at this URL: <a href=\"@url\">@url</a>."
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:132
+msgid "SimpleID will also be sending the following registration information to the site."
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:133
+#: www/extensions/sreg/sreg.extension.php:185
+msgid "Name"
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:182
+msgid "SimpleID will send the following information to sites which supports the Simple Registration Extension."
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:183
+msgid "If you have also supplied OpenID Connect user information in your identity file, these may also be sent as part of this Extension."
+msgstr ""
+
+#: www/extensions/sreg/sreg.extension.php:192
+msgid "Simple Registration Extension"
+msgstr ""
+
+#: www/extensions/ui/ui.extension.php:188
+msgid "Invalid UI icon parameters."
+msgstr ""
+
+#: www/extensions/ui/ui.extension.php:196
+msgid "Unable to get icon."
+msgstr ""
+
+#: www/index.php:42 www/upgrade.php:60
+msgid "No configuration file found. See the <a href=\"!url\">manual</a> for instructions on how to set up a configuration file."
+msgstr ""
+
+#: www/index.php:113 www/upgrade.php:117
+msgid "Documentation"
+msgstr ""
+
+#: www/index.php:114 www/upgrade.php:118
+msgid "Support"
+msgstr ""
+
+#: www/index.php:118 www/upgrade.php:121
+msgid "Identities directory not found. See the <a href=\"!url\">manual</a> for instructions on how to set up SimpleID."
+msgstr ""
+
+#: www/index.php:123 www/upgrade.php:125
+msgid "Cache directory not found or not writeable. See the <a href=\"!url\">manual</a> for instructions on how to set up SimpleID."
+msgstr ""
+
+#: www/index.php:129 www/upgrade.php:129
+msgid "Store directory not found or not writeable. See the <a href=\"!url\">manual</a> for instructions on how to set up SimpleID."
+msgstr ""
+
+#: www/index.php:134 www/upgrade.php:133
+msgid "register_globals is enabled in PHP configuration, which is not supported by SimpleID. See the <a href=\"!url\">manual</a> for further information."
+msgstr ""
+
+#: www/index.php:139 www/index.php:143 www/index.php:147 www/index.php:151
+#: www/index.php:155 www/upgrade.php:138 www/upgrade.php:142
+#: www/upgrade.php:146 www/upgrade.php:150 www/upgrade.php:154
+#, php-format
+msgid "One or more required PHP extensions (%extension) is not loaded. See the <a href=\"!url\">manual</a> for further information on system requirements."
+msgstr ""
+
+#: www/index.php:159 www/upgrade.php:158
+msgid "suhosin.get.max_value_length is less than 1024, which will lead to problems. See the <a href=\"!url\">manual</a> for further information on system requirements."
+msgstr ""
+
+#: www/index.php:279
+msgid "Invalid OpenID message."
+msgstr ""
+
+#: www/index.php:463
+msgid "Protocol Error: openid.return_to not set."
+msgstr ""
+
+#: www/index.php:468
+msgid "Protocol Error: openid.identity not set."
+msgstr ""
+
+#: www/index.php:476
+msgid "Protocol Error: openid.identity set, but not openid.claimed_id."
+msgstr ""
+
+#: www/index.php:482
+msgid "Protocol Error: openid.return_to not set when openid.realm is not set."
+msgstr ""
+
+#: www/index.php:1031 www/index.php:1112 www/user.inc.php:186
+#: www/user.inc.php:485
+msgid "Cancel"
+msgstr ""
+
+#: www/index.php:1036
+msgid "Unable to log into <strong class=\"realm\">@realm</strong>."
+msgstr ""
+
+#: www/index.php:1037
+msgid "Your current identity does not match the requested identity %identity."
+msgstr ""
+
+#: www/index.php:1038
+msgid "<a href=\"!url\">Switch to a different user</a> and try again."
+msgstr ""
+
+#: www/index.php:1050
+msgid "Warning: This web site has not confirmed its identity and might be fraudulent. Do not share any personal information with this web site unless you are sure it is legitimate. See the <a href=\"!url\" class=\"popup\">SimpleID documentation for details</a> (OpenID version 2.0 return_to discovery failure)"
+msgstr ""
+
+#: www/index.php:1057
+msgid "You are being logged into <strong class=\"realm\">@realm</strong>."
+msgstr ""
+
+#: www/index.php:1058
+msgid "Automatically send my information to this site for any future requests."
+msgstr ""
+
+#: www/index.php:1059
+msgid "OK"
+msgstr ""
+
+#: www/index.php:1066 www/page.inc.php:186
+msgid "This web site has not confirmed its identity and might be fraudulent."
+msgstr ""
+
+#: www/index.php:1066 www/page.inc.php:186
+msgid "Are you sure you wish to automatically send your information to this site for any future requests?"
+msgstr ""
+
+#: www/index.php:1073 www/index.php:1098
+msgid "OpenID Login"
+msgstr ""
+
+#: www/index.php:1097 www/page.inc.php:123 www/upgrade.php:211
+#: www/upgrade.php:262 www/user.inc.php:573 www/user.inc.php:589
+msgid "SimpleID detected a potential security attack. Please try again."
+msgstr ""
+
+#: www/index.php:1114
+msgid "Log in cancelled."
+msgstr ""
+
+#: www/index.php:1142
+msgid "You were logged in successfully."
+msgstr ""
+
+#: www/page.inc.php:57 www/page.inc.php:222
+msgid "Dashboard"
+msgstr ""
+
+#: www/page.inc.php:88
+msgid "<em>You need to set at least one of OpenID 1.x or OpenID 2 to generate the code.</em>"
+msgstr ""
+
+#: www/page.inc.php:92 www/page.inc.php:223
+msgid "My Profile"
+msgstr ""
+
+#: www/page.inc.php:149
+msgid "Your preferences have been saved."
+msgstr ""
+
+#: www/page.inc.php:178
+msgid "Site"
+msgstr ""
+
+#: www/page.inc.php:179
+msgid "Last access"
+msgstr ""
+
+#: www/page.inc.php:180
+msgid "Automatic"
+msgstr ""
+
+#: www/page.inc.php:181
+msgid "Remove"
+msgstr ""
+
+#: www/page.inc.php:182
+msgid "Submit"
+msgstr ""
+
+#: www/page.inc.php:189 www/page.inc.php:224
+msgid "My Sites"
+msgstr ""
+
+#: www/page.inc.php:265
+msgid "Welcome"
+msgstr ""
+
+#: www/page.inc.php:266
+msgid "You are logged in as %uid (%identity)."
+msgstr ""
+
+#: www/page.inc.php:280 www/user.inc.php:625
+msgid "Login verification adds an extra layer of protection to your account. When enabled, you will need to enter an additional security code whenever you log into SimpleID."
+msgstr ""
+
+#: www/page.inc.php:283
+msgid "Login verification is <strong>enabled</strong>."
+msgstr ""
+
+#: www/page.inc.php:285 www/user.inc.php:571
+msgid "Disable"
+msgstr ""
+
+#: www/page.inc.php:287
+msgid "Login verification is <strong>disabled</strong>. To enable login verification, click the button below."
+msgstr ""
+
+#: www/page.inc.php:289
+msgid "Enable"
+msgstr ""
+
+#: www/page.inc.php:294 www/user.inc.php:642
+msgid "Login Verification"
+msgstr ""
+
+#: www/page.inc.php:307
+msgid "<link> tags"
+msgstr ""
+
+#: www/page.inc.php:309
+msgid "OpenID 1.x"
+msgstr ""
+
+#: www/page.inc.php:310
+msgid "OpenID 2.0"
+msgstr ""
+
+#: www/page.inc.php:311
+msgid "Claim a different identifier"
+msgstr ""
+
+#: www/page.inc.php:319
+msgid "YADIS"
+msgstr ""
+
+#: www/page.inc.php:320
+msgid "Write your own or <a href=\"!url\">download</a> your YADIS document"
+msgstr ""
+
+#: www/page.inc.php:321
+msgid "Add HTTP headers or <meta> tag, e.g.:"
+msgstr ""
+
+#: www/page.inc.php:326
+msgid "Claim your Identifier"
+msgstr ""
+
+#: www/upgrade.php:185
+msgid "Use this script to update your installation whenever you upgrade to a new version of SimpleID."
+msgstr ""
+
+#: www/upgrade.php:186 www/upgrade.php:395
+msgid "For more detailed information, see the <a href=\"!url\">SimpleID documentation</a>."
+msgstr ""
+
+#: www/upgrade.php:187
+msgid "<strong>Back up your installation</strong>. This process will change various files within your SimpleID installation and in case of emergency you may need to revert to a backup."
+msgstr ""
+
+#: www/upgrade.php:188
+msgid "Install your new files in the appropriate location, as described in the <a href=\"!url\">SimpleID documentation</a>."
+msgstr ""
+
+#: www/upgrade.php:189
+msgid "When you have performed the steps above, click <strong>Continue</strong>."
+msgstr ""
+
+#: www/upgrade.php:190 www/upgrade.php:235
+msgid "Continue"
+msgstr ""
+
+#: www/upgrade.php:194 www/upgrade.php:249 www/upgrade.php:286
+msgid "Upgrade"
+msgstr ""
+
+#: www/upgrade.php:220 www/upgrade.php:275
+msgid "Remember to edit upgrade.php to check <code>$upgrade_access_check</code> back to <code>FALSE</code>."
+msgstr ""
+
+#: www/upgrade.php:224
+msgid "Your SimpleID installation is up-to-date. This script is complete."
+msgstr ""
+
+#: www/upgrade.php:234
+msgid "Click <strong>Continue</strong> to proceed with the upgrade."
+msgstr ""
+
+#: www/upgrade.php:243
+msgid "The version of SimpleID you are updating from has been automatically detected."
+msgstr ""
+
+#: www/upgrade.php:244
+msgid "Original version"
+msgstr ""
+
+#: www/upgrade.php:245
+msgid "Upgrade version"
+msgstr ""
+
+#: www/upgrade.php:280
+msgid "Your SimpleID installation has been upgraded. Please check the results below for any errors."
+msgstr ""
+
+#: www/upgrade.php:389
+msgid "Access denied. You are not authorised to access this page. Please <a href=\"index.php?q=login\">log in</a> as an administrator (a user whose identity file includes the line <code>administrator=1</code>)."
+msgstr ""
+
+#: www/upgrade.php:390
+msgid "If you cannot log in, you will have to edit <code>upgrade.php</code> to bypass this access check. To do this:"
+msgstr ""
+
+#: www/upgrade.php:391
+msgid "With a text editor find the upgrade.php file."
+msgstr ""
+
+#: www/upgrade.php:392
+msgid "There is a line inside your upgrade.php file that says <code>$upgrade_access_check = TRUE;</code>. Change it to <code>$upgrade_access_check = FALSE;</code>."
+msgstr ""
+
+#: www/upgrade.php:393
+msgid "As soon as the upgrade.php script is done, you must change the file back to its original form with <code>$upgrade_access_check = TRUE;</code>."
+msgstr ""
+
+#: www/upgrade.php:394
+msgid "To avoid having this problem in future, remember to log in to SimpleID as an administrator before you run this script."
+msgstr ""
+
+#: www/upgrade.php:399
+msgid "Access Denied"
+msgstr ""
+
+#: www/user.inc.php:197
+msgid "Login cancelled without a proper OpenID request."
+msgstr ""
+
+#: www/user.inc.php:203 www/user.inc.php:224 www/user.inc.php:234
+msgid "SimpleID detected a potential security attack on your log in. Please log in again."
+msgstr ""
+
+#: www/user.inc.php:211
+msgid "You seem to be attempting to log in from another web page. You must use this page to log in."
+msgstr ""
+
+#: www/user.inc.php:229
+msgid "The log in page has expired. Please log in again."
+msgstr ""
+
+#: www/user.inc.php:248
+msgid "You need to supply the user name and the password in order to log in."
+msgstr ""
+
+#: www/user.inc.php:256
+msgid "The user name or password is not correct."
+msgstr ""
+
+#: www/user.inc.php:270
+msgid "You need to enter the verification code in order to log in."
+msgstr ""
+
+#: www/user.inc.php:279 www/user.inc.php:595
+msgid "The verification code is not correct."
+msgstr ""
+
+#: www/user.inc.php:443
+msgid "You have been logged out."
+msgstr ""
+
+#: www/user.inc.php:503
+msgid "Secure login using <strong>HTTPS</strong>."
+msgstr ""
+
+#: www/user.inc.php:506
+msgid "<strong>WARNING:</strong> Your password will be sent to SimpleID as plain text."
+msgstr ""
+
+#: www/user.inc.php:513
+msgid "User name:"
+msgstr ""
+
+#: www/user.inc.php:514
+msgid "Password:"
+msgstr ""
+
+#: www/user.inc.php:515
+msgid "Remember me on this computer for two weeks."
+msgstr ""
+
+#: www/user.inc.php:525
+msgid "Log in"
+msgstr ""
+
+#: www/user.inc.php:526
+msgid "Log In"
+msgstr ""
+
+#: www/user.inc.php:530
+msgid "To verify your identity, enter the verification code."
+msgstr ""
+
+#: www/user.inc.php:531
+msgid "If you have lost your verification code, you can <a href=\"!url\">recover your account</a>."
+msgstr ""
+
+#: www/user.inc.php:535 www/user.inc.php:638
+msgid "Verification code:"
+msgstr ""
+
+#: www/user.inc.php:539 www/user.inc.php:585 www/user.inc.php:639
+msgid "Verify"
+msgstr ""
+
+#: www/user.inc.php:540
+msgid "Enter Verification Code"
+msgstr ""
+
+#: www/user.inc.php:593
+msgid "You need to enter the verification code to complete enabling login verification."
+msgstr ""
+
+#: www/user.inc.php:626
+msgid "<strong>WARNING:</strong> If you enable login verification and lose your authenticator app, you will need to <a href=\"!url\">edit your identity file manually</a> before you can log in again."
+msgstr ""
+
+#: www/user.inc.php:630
+msgid "To set up login verification, following these steps."
+msgstr ""
+
+#: www/user.inc.php:631
+msgid "Download an authenticator app that supports TOTP for your smartphone, such as Google Authenticator."
+msgstr ""
+
+#: www/user.inc.php:632
+msgid "Add your SimpleID account to authenticator app using this key. If you are viewing this page on your smartphone you can use <a href=\"!url\">this link</a> or scan the QR code to add your account."
+msgstr ""
+
+#: www/user.inc.php:635
+msgid "To check that your account has been added properly, enter the verification code from your phone into the box below, and click Verify."
+msgstr ""
+
+#: www/user.inc.php:661
+msgid "User Page"
+msgstr ""
+
+#: www/user.inc.php:664
+msgid "No user specified."
+msgstr ""
+
+#: www/user.inc.php:670
+#, php-format
+msgid "User %uid not found."
+msgstr ""
+
+#: www/user.inc.php:682
+#, php-format
+msgid "This is the user %uid's SimpleID page. It contains hidden information for the use by OpenID consumers."
+msgstr ""
+
+#: www/user.inc.php:724
+msgid "Private Personal Identifier"
+msgstr ""
+
+#: www/user.inc.php:726
+msgid "This is a private personal identifier."
+msgstr ""
+
+#: www/user.inc.php:778
+msgid "SimpleID may, with your consent, send the following information to sites which supports OpenID Connect."
+msgstr ""
+
+#: www/user.inc.php:781
+msgid "Member"
+msgstr ""
+
+#: www/user.inc.php:799
+msgid "OpenID Connect"
+msgstr ""
+
+#: www/user.inc.php:819
+msgid "Log out and log in as a different user"
+msgstr ""
+
+#: www/user.inc.php:822
+msgid "Log out"
+msgstr ""
diff --git a/simpleid/www/log.inc.php b/simpleid/www/log.inc.php
new file mode 100644
index 0000000..9e1caf0
--- /dev/null
+++ b/simpleid/www/log.inc.php
@@ -0,0 +1,210 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-9
+ *
+ * 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$
+ */
+
+/**
+ * Functions for logging.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+/** Log level */
+define('SIMPLEID_LOG_DEBUG', 5);
+/** Log level */
+define('SIMPLEID_LOG_INFO', 4);
+/** Log level */
+define('SIMPLEID_LOG_NOTICE', 3);
+/** Log level */
+define('SIMPLEID_LOG_WARN', 2);
+/** Log level */
+define('SIMPLEID_LOG_ERROR', 1);
+/** Log level */
+define('SIMPLEID_LOG_FATAL', 0);
+
+/**
+ * This variable holds the pointer to the currently opened log file. If the
+ * log file is not open, this variable is NULL.
+ *
+ * @global resource $log
+ */
+$log = NULL;
+
+
+/**
+ * Opens the log file.
+ *
+ * This function opens a pointed to the log file for later usage.
+ *
+ * @return bool true if the log file is opened successfully.
+ */
+function log_open() {
+ global $log;
+ if (!defined('SIMPLEID_LOGFILE') || (SIMPLEID_LOGFILE == '')) return;
+ $log = fopen(SIMPLEID_LOGFILE, 'a');
+
+ if ($log === false) {
+ $log = NULL;
+ return false;
+ } else {
+ return true;
+ }
+}
+
+/**
+ * Closes the log file, if it is open.
+ */
+function log_close() {
+ if ($log != NULL) {
+ fflush($log);
+ fclose($log);
+ $log = NULL;
+ }
+}
+
+/**
+ * Logs a DEBUG message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_debug($message) {
+ _log_write($message, SIMPLEID_LOG_DEBUG);
+}
+
+/**
+ * Logs an INFO message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_info($message) {
+ _log_write($message, SIMPLEID_LOG_INFO);
+}
+
+/**
+ * Logs a NOTICE message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_notice($message) {
+ _log_write($message, SIMPLEID_LOG_NOTICE);
+}
+
+/**
+ * Logs a WARN message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_warn($message) {
+ _log_write($message, SIMPLEID_LOG_WARN);
+}
+
+/**
+ * Logs a ERROR message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_error($message) {
+ _log_write($message, SIMPLEID_LOG_ERROR);
+}
+
+/**
+ * Logs a FATAL message.
+ *
+ * @param string $message the message to log
+ * @see _log_write()
+ */
+function log_fatal($message) {
+ _log_write($message, SIMPLEID_LOG_FATAL);
+}
+
+/**
+ * Converts an array into a string for logging purposes.
+ *
+ * @param array $array the array the convert
+ * @param array $keys an array of keys to include in the converted string. Set
+ * to false if all the keys in the array should be included
+ * @return string the converted string.
+ */
+function log_array($array, $keys = false) {
+ $output = array();
+
+ if ($keys == false) $keys = array_keys($array);
+
+ foreach ($keys as $key) {
+ $output[] = $key . ": " . $array[$key];
+ }
+
+ return implode('; ', $output);
+}
+
+/**
+ * Logs a message
+ *
+ * @param string $message the message to log
+ * @param int $level the log level
+ * @return bool true if the log has been written successfully
+ */
+function _log_write($message, $level = false) {
+ global $log;
+ static $levels;
+
+ if (!$levels) {
+ $levels = array(
+ SIMPLEID_LOG_DEBUG => 'DEBUG',
+ SIMPLEID_LOG_INFO => 'INFO',
+ SIMPLEID_LOG_NOTICE => 'NOTICE',
+ SIMPLEID_LOG_WARN => 'WARN',
+ SIMPLEID_LOG_ERROR => 'ERROR',
+ SIMPLEID_LOG_FATAL => 'FATAL'
+ );
+ }
+
+ /* If a priority hasn't been specified, use the default value. */
+ if ($level === false) {
+ $level = SIMPLEID_LOG_INFO;
+ }
+
+ /* Abort early if the priority is above the maximum logging level. */
+ if ($level > SIMPLEID_LOGLEVEL) {
+ return false;
+ }
+
+ /* If the log file isn't already open, open it now. */
+ if (($log == NULL) && !log_open()) {
+ return false;
+ }
+
+ /* Build the string containing the complete log line. */
+ $line = sprintf('%1$s %2$s [%3$s] %4$s', strftime(SIMPLEID_DATE_TIME_FORMAT), session_id(), $levels[$level], $message) . "\n";
+
+ /* Write the log line to the log file. */
+ $success = (fwrite($log, $line) !== false);
+
+ return $success;
+}
+
+?>
diff --git a/simpleid/www/openid.inc.php b/simpleid/www/openid.inc.php
new file mode 100644
index 0000000..8507804
--- /dev/null
+++ b/simpleid/www/openid.inc.php
@@ -0,0 +1,1054 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-10
+ *
+ * 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$
+ */
+
+/**
+ * OpenID related functions.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+include_once "bignum.inc.php";
+include_once "random.inc.php";
+
+/**
+ * OpenID default modulus for Diffie-Hellman key exchange.
+ *
+ * @link http://openid.net/specs/openid-authentication-1_1.html#pvalue, http://openid.net/specs/openid-authentication-2_0.html#pvalue
+ */
+define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
+ '966915404479707795314057629378541917580651227423698188993727816152646631'.
+ '438561595825688188889951272158842675419950341258706556549803580104870537'.
+ '681476726513255747040765857479291291572334510643245094715007229621094194'.
+ '349783925984760375594985848253359305585439638443');
+
+/**
+ * OpenID default generator for Diffie-Hellman key exchange.
+ */
+define('OPENID_DH_DEFAULT_GEN', '2');
+
+/** Constant for the global variable {@link $version} */
+define('OPENID_VERSION_2', 2);
+/** Constant for the global variable {@link $version} */
+define('OPENID_VERSION_1_1', 1);
+
+/** Constant for OpenID namespace */
+define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
+/** Constant for OpenID namespace */
+define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
+/** Constant for OpenID namespace */
+define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
+
+/**
+ * Constant for the OP-local identifier which indicates that SimpleID should choose an identifier
+ *
+ * @link http://openid.net/specs/openid-authentication-2_0.html#anchor27
+ */
+define('OPENID_IDENTIFIER_SELECT', 'http://specs.openid.net/auth/2.0/identifier_select');
+/** Constant for the XRDS service type for return_to verification */
+define('OPENID_RETURN_TO', 'http://specs.openid.net/auth/2.0/return_to');
+
+/** Parameter for {@link openid_indirect_response_url()} */
+define('OPENID_RESPONSE_QUERY', 0);
+/** Parameter for {@link openid_indirect_response_url()} */
+define('OPENID_RESPONSE_FRAGMENT', 1);
+
+/**
+ * A mapping of Type URIs of OpenID extnesions to aliases provided in an OpenID
+ * request.
+ *
+ * @global array $openid_ns_to_alias
+ */
+$openid_ns_to_alias = array("http://openid.net/extensions/sreg/1.1" => "sreg"); // For sreg 1.0 compatibility
+
+
+/**
+ * Detects the OpenID version of the current request
+ *
+ * @param mixed $request the OpenID request
+ * @param string $key the key to look for to determine the OpenID
+ * version
+ * @return float either OPENID_VERSION_2 or OPENID_VERSION_1_1
+ * @see $version
+ *
+ */
+function openid_get_version($request, $key = 'openid.ns') {
+ if (!isset($request[$key])) return OPENID_VERSION_1_1;
+ if ($request[$key] != OPENID_NS_2_0) return OPENID_VERSION_1_1;
+ return OPENID_VERSION_2;
+}
+
+/**
+ * Creates a OpenID message for direct response.
+ *
+ * The response will be encoded using Key-Value Form Encoding.
+ *
+ * @param array $data the data in the response
+ * @param float $version the message version
+ * @return string the message in key-value form encoding
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor32, http://openid.net/specs/openid-authentication-2_0.html#kvform
+ */
+function openid_direct_message($data, $version = OPENID_VERSION_2) {
+ $message = '';
+ $ns = '';
+
+ // Add namespace for OpenID 2
+ if ($version == OPENID_VERSION_2) $ns = OPENID_NS_2_0;
+ if (($ns != '') && !isset($data['ns'])) $data['ns'] = $ns;
+
+ foreach ($data as $key => $value) {
+ // Filter out invalid characters
+ if (strpos($key, ':') !== false) return null;
+ if (strpos($key, "\n") !== false) return null;
+ if (strpos($value, "\n") !== false) return null;
+
+ $message .= "$key:$value\n";
+ }
+ return $message;
+}
+
+/**
+ * Sends a direct response.
+ *
+ * @param string $message an OpenID message encoded using Key-Value Form
+ * @param string $status the HTTP status to send
+ */
+function openid_direct_response($message, $status = '200 OK') {
+ if (substr(PHP_SAPI, 0, 3) === 'cgi') {
+ header("Status: $status");
+ } else {
+ header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status);
+ }
+
+ header("Content-Type: text/plain");
+ print $message;
+}
+
+/**
+ * Creates a OpenID message for indirect response.
+ *
+ * The response will be encoded using HTTP Encoding.
+ *
+ * @param array $data the data in the response
+ * @param float $version the message version
+ * @return array the message
+ * @link http://openid.net/specs/openid-authentication-2_0.html#indirect_comm
+ */
+function openid_indirect_message($data, $version = OPENID_VERSION_2) {
+ $ns = '';
+
+ // Add namespace for OpenID 2
+ if ($version == OPENID_VERSION_2) $ns = OPENID_NS_2_0;
+ if (($ns != '') && !isset($data['openid.ns'])) $data['openid.ns'] = $ns;
+
+ return $data;
+}
+
+/**
+ * Sends an indirect response to a URL.
+ *
+ * The indirect message is encoded in the URL and returned to the user agent using
+ * a HTTP redirect response. The message can be encoded in either the query component
+ * or the fragment component of the URL.
+ *
+ * @param string $url the URL to which the response is to be sent
+ * @param array|string $message an OpenID message, which can either be an array of keys
+ * and values, or a URL-encoded query string
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ */
+function openid_indirect_response($url, $message, $component = OPENID_RESPONSE_QUERY) {
+ if (substr(PHP_SAPI, 0,3) === 'cgi') {
+ header('Status: 303 See Other');
+ } else {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
+ }
+
+ header('Location: ' . openid_indirect_response_url($url, $message, $component));
+ exit;
+}
+
+/**
+ * Encodes an indirect message into a URL
+ *
+ * @param string $url the URL to which the response is to be sent
+ * @param array|string $message an OpenID message, which can either be an array of keys
+ * and values, or a URL-encoded query string
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ * @return string the URL to which the response is to be sent, with the
+ * encoded message
+ */
+function openid_indirect_response_url($url, $message, $component = OPENID_RESPONSE_QUERY) {
+ // 1. Firstly, get the query string
+ $query = '';
+
+ if (is_array($message)) {
+ $query = openid_urlencode_message($message);
+ } else {
+ $query = $message;
+ }
+
+ // 2. If there is no query string, then we just return the URL
+ if (!$query) return $url;
+
+ // 3. The URL may already have a query and a fragment. If this is so, we
+ // need to slot in the new query string properly. We disassemble and
+ // reconstruct the URL.
+ $parts = parse_url($url);
+
+ $url = $parts['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'];
+ if (isset($parts['path'])) $url .= $parts['path'];
+
+ if (($component == OPENID_RESPONSE_QUERY) || (strpos($url, '#') === FALSE)) {
+ $url .= '?' . ((isset($parts['query'])) ? $parts['query'] . '&' : '') . $query;
+ if (isset($parts['fragment'])) $url .= '#' . $parts['fragment'];
+ } elseif ($component == OPENID_RESPONSE_FRAGMENT) {
+ // In theory $parts['fragment'] should be an empty string, but the
+ // current draft specification does not prohibit putting other things
+ // in the fragment.
+
+ if (isset($parts['query'])) {
+ $url .= '?' . $parts['query'] . '#' . $parts['fragment'] . '&' . $query;
+ } else {
+ $url .= '#' . $parts['fragment'] . '?' . $query;
+ }
+ }
+ return $url;
+}
+
+/**
+ * Encodes a message in application/x-www-form-urlencoded format.
+ *
+ * @param array $message the OpenID message to encode
+ * @return string the encoded message
+ * @since 0.8
+ */
+function openid_urlencode_message($message) {
+ $pairs = array();
+
+ foreach ($message as $key => $value) {
+ $pairs[] = $key . '=' . rfc3986_urlencode($value);
+ }
+
+ return implode('&', $pairs);
+}
+
+/**
+ * Sends a direct message indicating an error. This is a convenience function
+ * for {@link openid_direct_response()}.
+ *
+ * @param string $error the error message
+ * @param array $additional any additional data to be sent with the error
+ * message
+ * @param float $version the message version
+ */
+function openid_direct_error($error, $additional = array(), $version = OPENID_VERSION_2) {
+ $message = openid_direct_message(array_merge(array('error' => $error), $additional), $version);
+ openid_direct_response($message, '400 Bad Request');
+}
+
+/**
+ * Sends an indirect message indicating an error. This is a convenience function
+ * for {@link openid_indirect_response()}.
+ *
+ * @param string $url the URL to which the error message is to be sent
+ * @param string $error the error message
+ * @param array $additional any additional data to be sent with the error
+ * message
+ * @param float $version the message version
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ */
+function openid_indirect_error($url, $error, $additional = array(), $version = OPENID_VERSION_2, $component = OPENID_RESPONSE_QUERY) {
+ $message = openid_indirect_message(array_merge(array('openid.mode'=> 'error', 'openid.error' => $error), $additional), $version);
+ openid_indirect_response($url, $message, $component);
+}
+
+/**
+ * Gets the realm from the OpenID request. This is specified differently
+ * depending on the OpenID version.
+ *
+ * @param mixed $request the OpenID request
+ * @param float $version the OpenID version for the message
+ * @return string the realm URI
+ */
+function openid_get_realm($request, $version) {
+ if ($version == OPENID_VERSION_1_1) {
+ $realm = $request['openid.trust_root'];
+ }
+
+ if ($version >= OPENID_VERSION_2) {
+ $realm = $request['openid.realm'];
+ }
+
+ if (!$realm) {
+ $realm = $request['openid.return_to'];
+ }
+
+ return $realm;
+}
+
+/**
+ * Parses a direct message.
+ *
+ * @param string $message the direct message to parse
+ * @return array an array containing the parsed key-value pairs
+ *
+ * @since 0.7
+ */
+function openid_parse_direct_message($message) {
+ $data = array();
+
+ $items = explode("\n", $message);
+ foreach ($items as $item) {
+ list ($key, $value) = explode(':', $item, 2);
+ $data[$key] = $value;
+ }
+
+ return $data;
+}
+
+/**
+ * Parses a query string.
+ *
+ * Query strings can be used to receive OpenID indirect messages.
+ *
+ * @param string $query the query string to parse
+ * @return array an array containing the parsed key-value pairs
+ *
+ * @since 0.7
+ */
+function openid_parse_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;
+}
+
+/**
+ * Parses the OpenID request to extract namespace information.
+ *
+ * This function builds a map between namespace aliases and their Type URIs.
+ *
+ * @param array $request the OpenID request
+ */
+function openid_parse_request($request) {
+ global $openid_ns_to_alias;
+
+ foreach ($request as $key => $value) {
+ if (strpos($key, 'openid.ns.') === 0) {
+ $alias = substr($key, 10);
+ $openid_ns_to_alias[$value] = $alias;
+ }
+ }
+}
+
+/**
+ * Determines whether a URL matches a realm.
+ *
+ * A URL matches a realm if:
+ *
+ * 1. The URL scheme and port of the URL are identical to those in the realm.
+ * See RFC 3986, section 3.1 for rules about URI matching.
+ * 2. The URL's path is equal to or a sub-directory of the realm's path.
+ * 3. Either:
+ * (a) The realm's domain contains the wild-card characters "*.", and the
+ * trailing part of the URL's domain is identical to the part of the
+ * realm following the "*." wildcard, or
+ * (b) The URL's domain is identical to the realm's domain
+ *
+ * @param string $url to URL to test
+ * @param string $realm the realm
+ * @return bool true if the URL matches the realm
+ * @since 0.6
+ */
+function openid_url_matches_realm($url, $realm) {
+ $url = parse_url($url);
+ $realm = parse_url($realm);
+
+ foreach(array('user', 'pass', 'fragment') as $key) {
+ if (array_key_exists($key, $url) || array_key_exists($key, $realm))
+ return false;
+ }
+
+ if ($url['scheme'] != $realm['scheme']) return false;
+
+ if (!isset($url['port']))
+ $url['port'] = '';
+ if (!isset($realm['port']))
+ $realm['port'] = '';
+ if (($url['port'] != $realm['port']))
+ return false;
+
+ if (substr($realm['host'], 0, 2) == '*.') {
+ $realm_re = '/^([^.]+\.)?' . preg_quote(substr($realm['host'], 2)) . '$/i';
+ } else {
+ $realm_re = '/^' . preg_quote($realm['host']) . '$/i';
+ }
+
+ if (!preg_match($realm_re, $url['host'])) return false;
+
+ if (!isset($url['path']))
+ $url['path'] = '';
+ if (!isset($realm['path']))
+ $realm['path'] = '';
+ if (substr($realm['path'], -1) == '/') $realm['path'] = substr($realm['path'], 0, -1);
+ if (($url['path'] != $realm['path']) && !preg_match('#^' . preg_quote($realm['path']) . '/.*$#', $url['path'])) return false;
+
+ return true;
+}
+
+/**
+ * Returns the URL of a relying party endpoint for a specified realm. This URL
+ * is used to discover services associated with the realm.
+ *
+ * If the realm's domain contains the wild-card characters "*.", this is substituted
+ * with "www.".
+ *
+ * @param string $realm the realm
+ * @url string the URL
+ *
+ * @since 0.7
+ */
+function openid_realm_discovery_url($realm) {
+ $parts = parse_url($realm);
+ $host = strtr($parts['host'], array('*.' => 'www.'));;
+
+ $url = $parts['scheme'] . '://';
+ if (isset($parts['user'])) {
+ $url .= $parts['user'];
+ if (isset($parts['pass'])) $url .= ':' . $parts['pass'];
+ $url .= '@';
+ }
+ $url .= $host;
+ if (isset($parts['port'])) $url .= ':' . $parts['port'];
+ if (isset($parts['path'])) $url .= $parts['path'];
+ if (isset($parts['query'])) $url .= '?' . $parts['query'];
+ if (isset($parts['fragment'])) $url .= '#' . $parts['fragment'];
+ return $url;
+}
+
+/**
+ * Verifies a return_to URL against the actual URL of the HTTP request.
+ *
+ * The return_to URL matches if:
+ *
+ * - The URL scheme, authority, and path are the same; and
+ * - Any query parameters that are present in the return_to URL are also present
+ * with the same values in the actual request.
+ *
+ * @param string $return_to the URL specified in the openid.return_to parameter
+ * @param string $actual_url the actual URL requested
+ * @return bool true if the URLs match
+ *
+ * @since 0.7
+ */
+function openid_verify_return_to($return_to, $actual_url) {
+ $expected = parse_url($return_to);
+ $actual = parse_url($actual_url);
+
+ // Schemes are case insensitive
+ if (strtoupper($expected['scheme']) != strtoupper($actual['scheme'])) return false;
+
+ // Hosts are case insensitive
+ if (strtoupper($expected['host']) != strtoupper($actual['host'])) return false;
+
+ if (!isset($expected['port']))
+ $expected['port'] = '';
+ if (!isset($actual['port']))
+ $actual['port'] = '';
+ if ($expected['port'] != $actual['port']) return false;
+
+ if (!isset($expected['path']))
+ $expected['path'] = '';
+ if (!isset($actual['path']))
+ $actual['path'] = '';
+ if ($expected['path'] != $actual['path']) return false;
+
+ if ($expected['query']) {
+ $expected_query = openid_parse_query($expected['query']);
+ $actual_query = openid_parse_query($actual['query']);
+
+ foreach ($expected_query as $key => $value) {
+ if (!array_key_exists($key, $actual_query)) return false;
+ if ($value != $actual_query[$key]) return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Filters an OpenID request to find keys specific to an extension, as specified
+ * by the Type URI.
+ *
+ * For exmaple, if the extension has the Type URI http://example.com/ and the
+ * alias example, this function will return an array of all the keys in the
+ * OpenID request which starts with openid.example
+ *
+ * @param string $ns the Type URI of the extension
+ * @param array $request the OpenID request
+ * @return array the filtered request, with the prefix (in the example above,
+ * openid.example.) stripped in the keys.
+ */
+function openid_extension_filter_request($ns, $request) {
+ global $openid_ns_to_alias;
+
+ if (!isset($openid_ns_to_alias[$ns])) return array();
+
+ $alias = $openid_ns_to_alias[$ns];
+ $return = array();
+
+ if (is_array($request)) {
+ foreach ($request as $key => $value) {
+ if ($key == 'openid.' . $alias) {
+ $return['#default'] = $value;
+ }
+ if (strpos($key, 'openid.' . $alias . '.') === 0) {
+ $return[substr($key, strlen('openid.' . $alias . '.'))] = $value;
+ }
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Determines whether an extension is present in an OpenID request.
+ *
+ * @param string $ns the Type URI of the extension
+ * @param array $request the OpenID request
+ * @return bool true if the extension is present in the request
+ */
+function openid_extension_requested($ns, $request) {
+ global $openid_ns_to_alias;
+
+ if (!isset($openid_ns_to_alias[$ns])) return false;
+ $alias = $openid_ns_to_alias[$ns];
+
+ if (is_array($request)) {
+ foreach ($request as $key => $value) {
+ if ((strpos($key, 'openid.' . $alias . '.') === 0) || (strpos($key, 'openid.' . $alias . '=') === 0)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Returns the OpenID alias for an extension, given a Type URI, based on the
+ * alias definitions in the current OpenID request.
+ *
+ * @param string $ns the Type URI
+ * @param bool|string $create whether to create an alias if the Type URI does not already
+ * have an alias in the current OpenID request. If this parameter is a string,
+ * then the string specified is the preferred alias to be created, unless a collision
+ * occurs
+ * @return string the alias, or NULL if the Type URI does not already
+ * have an alias in the current OpenID request <i>and</i> $create is false
+ */
+function openid_extension_alias($ns, $create = FALSE) {
+ global $openid_ns_to_alias;
+ static $e = 1;
+
+ if (isset($openid_ns_to_alias[$ns])) return $openid_ns_to_alias[$ns];
+ if ($create !== FALSE) {
+ if ($create === TRUE) {
+ $alias = 'e' . $e;
+ $e++;
+ } elseif (is_string($create)) {
+ $used_aliases = array_values($openid_ns_to_alias);
+
+ $alias = $create;
+ $i = 0;
+
+ while (in_array($alias, $used_aliases)) {
+ $i++;
+ $alias = $create . $i;
+ }
+ }
+ $openid_ns_to_alias[$ns] = $alias;
+ return $alias;
+ }
+ return NULL;
+}
+
+
+/* ------- OpenID nonce functions -------------------------------------------- */
+/**
+ * Generates a nonce for use in OpenID responses
+ *
+ * @return string an OpenID nonce
+ * @link http://openid.net/specs/openid-authentication-2_0.html#positive_assertions
+ */
+function openid_nonce() {
+ return gmstrftime('%Y-%m-%dT%H:%M:%SZ') . bin2hex(random_bytes(4));
+}
+
+/* ------- Diffie-Hellman Key Exchange functions ----------------------------- */
+
+/**
+ * Returns the association types supported by this server.
+ *
+ * @return array an array containing the association types supported by this server as keys
+ * and an array containing the key size (mac_size) and HMAC function (hmac_func) as
+ * values
+ */
+function openid_association_types() {
+ $association_types = array('HMAC-SHA1' => array('mac_size' => 20, 'hmac_func' => '_openid_hmac_sha1'));
+ if (OPENID_SHA256_SUPPORTED) $association_types['HMAC-SHA256'] = array('mac_size' => 32, 'hmac_func' => '_openid_hmac_sha256');
+ return $association_types;
+}
+
+/**
+ * Returns the association types supported by this server and the version of
+ * OpenID.
+ *
+ * OpenID version 1 supports an empty string as the session type. OpenID version 2
+ * reqires a session type to be sent.
+ *
+ * @param bool $is_https whether the transport layer encryption is used for the current
+ * connection
+ * @param float $version the OpenID version, either OPENID_VERSION_1_1 and OPENID_VERSION_2
+ * @return array an array containing the session types supported by this server as keys
+ * and an array containing the hash function (hash_func) as
+ * values
+ */
+function openid_session_types($is_https = FALSE, $version = OPENID_VERSION_2) {
+ $session_types = array(
+ 'DH-SHA1' => array('hash_func' => '_openid_sha1'),
+ );
+ if (OPENID_SHA256_SUPPORTED) $session_types['DH-SHA256'] = array('hash_func' => '_openid_sha256');
+ if (($version >= OPENID_VERSION_2) && ($is_https == TRUE)) {
+ // Under OpenID 2.0 no-encryption is only allowed if TLS is used
+ $session_types['no-encryption'] = array();
+ }
+ if ($version == OPENID_VERSION_1_1) $session_types[''] = array();
+ return $session_types;
+}
+
+/**
+ * Generates the cryptographic values required for responding to association
+ * requests
+ *
+ * This involves generating a key pair for the OpenID provider, then calculating
+ * the shared secret. The shared secret is then used to encrypt the MAC key.
+ *
+ * @param string $mac_key the MAC key, in binary representation
+ * @param string $dh_consumer_public the consumer's public key, in Base64 representation
+ * @param string $dh_modulus modulus - a large prime number
+ * @param string $dh_gen generator - a primitive root modulo
+ * @param string $hash_func the hash function
+ * @return array an array containing (a) dh_server_public - the server's public key (in Base64), and (b)
+ * enc_mac_key encrypted MAC key (in Base64), encrypted using the Diffie-Hellman shared secret
+ */
+function openid_dh_server_assoc($mac_key, $dh_consumer_public, $dh_modulus = NULL, $dh_gen = NULL, $hash_func = '_openid_sha1') {
+
+ // Generate a key pair for the server
+ $key_pair = openid_dh_generate_key_pair($dh_modulus, $dh_gen);
+
+ // Generate the shared secret
+ $ZZ = openid_dh_shared_secret($dh_consumer_public, $key_pair['private'], $dh_modulus);
+
+ return array(
+ 'dh_server_public' => $key_pair['public'],
+ 'enc_mac_key' => openid_encrypt_mac_key($ZZ, $mac_key, $hash_func)
+ );
+}
+
+/**
+ * Complete association by obtaining the session MAC key from the key obtained
+ * from the Diffie-Hellman key exchange
+ *
+ * @param string $enc_mac_key the encrypted session MAC key, in Base64 represnetation
+ * @param string $dh_server_public the server's public key, in Base64 representation
+ * @param string $dh_consumer_private the consumer's private key, in Base64 representation
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @param string $hash_func the hash function
+ * @return string the decrypted session MAC key, in Base64 representation
+ */
+function openid_dh_consumer_assoc($enc_mac_key, $dh_server_public, $dh_consumer_private, $dh_modulus = NULL, $hash_func = '_openid_sha1') {
+ // Retrieve the shared secret
+ $ZZ = openid_dh_shared_secret($dh_server_public, $dh_consumer_private, $dh_modulus);
+
+ // Decode the encrypted MAC key
+ $encrypted_mac_key = base64_decode($enc_mac_key);
+
+ return openid_encrypt_mac_key($ZZ, $encrypted_mac_key, $hash_func);
+}
+
+/**
+ * Calculates the shared secret for Diffie-Hellman key exchange.
+ *
+ * This is the second step in the Diffle-Hellman key exchange process. The other
+ * party (in OpenID 1.0 terms, the consumer) has already generated the public
+ * key ($dh_consumer_public) and sent it to this party (the server). The Diffie-Hellman
+ * modulus ($dh_modulus) and generator ($dh_gen) have either been sent or previously agreed.
+ *
+ * @param string $their_public the other party's public key, in Base64 representation
+ * @param string $my_private this party's private key, in Base64 representation
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @return resource the shared secret (as a bignum)
+ *
+ * @see openid_dh_generate_key_pair()
+ * @link http://www.ietf.org/rfc/rfc2631.txt RFC 2631
+ */
+function openid_dh_shared_secret($their_public, $my_private, $dh_modulus = NULL) {
+ // Decode the keys
+ $y = _openid_base64_to_bignum($their_public);
+ $x = _openid_base64_to_bignum($my_private);
+
+ if ($dh_modulus != NULL) {
+ $p = _openid_base64_to_bignum($dh_modulus);
+ } else {
+ $p = bignum_new(OPENID_DH_DEFAULT_MOD);
+ }
+
+ // Generate the shared secret = their public ^ my private mod p = my public ^ their private mod p
+ $ZZ = bignum_powmod($y, $x, $p);
+
+ return $ZZ;
+}
+
+/**
+ * Generates a key pair for Diffie-Hellman key exchange.
+ *
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @param string $dh_gen generator, in Base64 representation
+ * @return array an array containing: (a) private - the private key, in Base64
+ * and (b) public - the public key, in Base64
+ */
+function openid_dh_generate_key_pair($dh_modulus = NULL, $dh_gen = NULL) {
+ if ($dh_modulus != NULL) {
+ $p = _openid_base64_to_bignum($dh_modulus);
+ } else {
+ $p = bignum_new(OPENID_DH_DEFAULT_MOD);
+ }
+
+ if ($dh_gen != NULL) {
+ $g = _openid_base64_to_bignum($dh_gen);
+ } else {
+ $g = bignum_new(OPENID_DH_DEFAULT_GEN);
+ }
+
+ // Generate the private key - a random number which is less than p
+ $rand = _openid_dh_rand($p);
+ $x = bignum_add($rand, 1);
+
+ // Calculate the public key is g ^ private mod p
+ $y = bignum_powmod($g, $x, $p);
+
+ return array('private' => _openid_bignum_to_base64($x), 'public' => _openid_bignum_to_base64($y));
+}
+
+
+/**
+ * Encrypts/decrypts and encodes the MAC key.
+ *
+ * @param resource $ZZ the Diffie-Hellman key exchange shared secret as a bignum
+ * @param string $mac_key a byte stream containing the MAC key
+ * @param string $hash_func the hash function
+ * @return string the encrypted MAC key in Base64 representation
+ */
+function openid_encrypt_mac_key($ZZ, $mac_key, $hash_func = '_openid_sha1') {
+ // Encrypt/decrypt the MAC key using the shared secret and the hash function
+ $encrypted_mac_key = _openid_xor($ZZ, $mac_key, $hash_func);
+
+ // Encode the encrypted/decrypted MAC key
+ $enc_mac_key = base64_encode($encrypted_mac_key);
+
+ return $enc_mac_key;
+}
+
+/**
+ * Encrypts/decrypts using XOR.
+ *
+ * @param string $key the encryption key as a bignum. This is usually
+ * the shared secret (ZZ) calculated from the Diffie-Hellman key exchange
+ * @param string $plain_cipher the plaintext or ciphertext
+ * @param string $hash_func the hash function
+ * @return string the ciphertext or plaintext
+ */
+function _openid_xor($key, $plain_cipher, $hash_func = '_openid_sha1') {
+ $decoded_key = bignum_val($key, 256);
+ $hashed_key = call_user_func($hash_func, $decoded_key);
+
+ $cipher_plain = "";
+ for ($i = 0; $i < strlen($plain_cipher); $i++) {
+ $cipher_plain .= chr(ord($plain_cipher[$i]) ^ ord($hashed_key[$i]));
+ }
+
+ return $cipher_plain;
+}
+
+/**
+ * Generates a random integer, which will be used to derive a private key
+ * for Diffie-Hellman key exchange. The integer must be less than $stop
+ *
+ * @param resource $stop a prime number as a bignum
+ * @return resource the random integer as a bignum
+ */
+function _openid_dh_rand($stop) {
+ static $duplicate_cache = array();
+
+ // Used as the key for the duplicate cache
+ $rbytes = bignum_val($stop, 256);
+
+ if (array_key_exists($rbytes, $duplicate_cache)) {
+ list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+ } else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = strlen($rbytes) - 1;
+ } else {
+ $nbytes = strlen($rbytes);
+ }
+
+ $mxrand = bignum_pow(bignum_new(256), $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = bignum_mod($mxrand, $stop);
+
+ if (count($duplicate_cache) > 10) {
+ $duplicate_cache = array();
+ }
+
+ $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+ }
+
+ do {
+ $bytes = "\x00" . random_bytes($nbytes);
+ $n = bignum_new($bytes, 256);
+ // Keep looping if this value is in the low duplicated range
+ } while (bignum_cmp($n, $duplicate) < 0);
+
+ return bignum_mod($n, $stop);
+}
+
+/* ------- Arbitary precision arithmetic and conversion functions ------------ */
+/**
+ * Converts an arbitary precision integer, encoded in Base64, to a bignum
+ *
+ * @param string $str arbitary precision integer, encoded in Base64
+ * @return resource the string representation
+ */
+function _openid_base64_to_bignum($str) {
+ return bignum_new(base64_decode($str), 256);
+}
+
+/**
+ * Converts a string representation of an integer to an arbitary precision
+ * integer, then converts it to Base64 encoding.
+ *
+ * @param string $str the string representation
+ * @return string the Base64 encoded arbitary precision integer
+ */
+function _openid_bignum_to_base64($str) {
+ return base64_encode(bignum_val($str, 256));
+}
+
+/**
+ * Encode an integer as big-endian signed two's complement binary string.
+ *
+ * @param string $num the binary integer
+ * @return string the signed two's complement binary string
+ * @link http://openid.net/specs/openid-authentication-2_0.html#btwoc
+ */
+function _openid_btwoc($num) {
+ return pack('H*', $num);
+}
+
+/* ------- Hash and HMAC functions ------------------------------------------- */
+/**
+ * Calculates a signature of an OpenID message
+ *
+ * @param array $data the data in the message
+ * @param array $keys a list of keys in the message to be signed (without the
+ * 'openid.' prefix)
+ * @param string $mac_key the MAC key used to sign the message, in Base64 representation
+ * @param string $hmac_func the HMAC function used in the signing process
+ * @param float $version the OpenID version
+ * @return string the signature encoded in Base64
+ */
+function openid_sign($data, $keys, $mac_key, $hmac_func = '_openid_hmac_sha1', $version = OPENID_VERSION_2) {
+ $signature = '';
+ $sign_data = array();
+
+ foreach ($keys as $key) {
+ if (array_key_exists('openid.' . $key, $data)) {
+ $sign_data[$key] = $data['openid.' . $key];
+ }
+ }
+
+ $signature_base_string = _openid_signature_base_string($sign_data, $version);
+ $secret = base64_decode($mac_key);
+ $signature = call_user_func($hmac_func, $secret, $signature_base_string);
+
+ return base64_encode($signature);
+}
+
+/**
+ * Calculates the base string from which an OpenID signature is generated.
+ *
+ * OpenID versions 1 and 2 specify that messages are to be encoded using Key-Value
+ * Encoding when generating signatures. However, future OpenID version may
+ * specify different ways of encoding the message, such as OAuth.
+ *
+ * @param array $data the data to sign
+ * @param float $version the OpenID version
+ * @return string the signature base string
+ * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
+ */
+function _openid_signature_base_string($data, $version) {
+ switch ($version) {
+ case OPENID_VERSION_1_1:
+ case OPENID_VERSION_2:
+ // We set OPENID_VERSION_1_1 because we don't want to sign the namespace header
+ $signature_base_string = openid_direct_message($data, OPENID_VERSION_1_1);
+ break;
+ default:
+ // We set OPENID_VERSION_1_1 because we don't want to sign the namespace header
+ $signature_base_string = openid_direct_message($data, OPENID_VERSION_1_1);
+ }
+ return $signature_base_string;
+}
+
+/**
+ * Obtains the SHA1 hash of a string in binary representation.
+ *
+ * @param string $text the text to be hashed
+ * @return string the hash in binary representation
+ */
+function _openid_sha1($text) {
+ return sha1($text, true);
+}
+
+/**
+ * Obtains the keyed hash value using the HMAC method and the SHA1 algorithm
+ *
+ * @param string $key the key in binary representation
+ * @param string $text the text to be hashed
+ * @return string the hash in binary representation
+ */
+function _openid_hmac_sha1($key, $text) {
+ if (function_exists('hash_hmac') && function_exists('hash_algos') && (in_array('sha1', hash_algos()))) {
+ return hash_hmac('sha1', $text, $key, true);
+ } else {
+ if (!defined('OPENID_SHA1_BLOCKSIZE')) define('OPENID_SHA1_BLOCKSIZE', 64);
+
+ if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
+ $key = _openid_sha1($key);
+ }
+
+ $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
+ $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
+ $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
+ $hash1 = _openid_sha1(($key ^ $ipad) . $text);
+ $hmac = _openid_sha1(($key ^ $opad) . $hash1);
+ return $hmac;
+ }
+}
+
+// Check if SHA-256 support is available
+if (function_exists('hash_hmac') && function_exists('hash_algos') && (in_array('sha256', hash_algos()))) {
+
+ /**
+ * Whether the current installation of PHP supports SHA256. SHA256 is supported
+ * if the hash module is properly compiled and loaded into PHP.
+ */
+ define('OPENID_SHA256_SUPPORTED', true);
+
+ /**
+ * Obtains the SHA256 hash of a string in binary representation.
+ *
+ * @param string $text the text to be hashed
+ * @return string $hash the hash in binary representation
+ */
+ function _openid_sha256($text) {
+ return hash('sha256', $text, true);
+ }
+
+ /**
+ * Obtains the keyed hash value using the HMAC method and the SHA256 algorithm
+ *
+ * @param string $key the key in binary representation
+ * @param string $text the text to be hashed
+ * @return string the hash in binary representation
+ */
+ function _openid_hmac_sha256($key, $text) {
+ return hash_hmac('sha256', $text, $key, true);
+ }
+} else {
+ /** @ignore */
+ define('OPENID_SHA256_SUPPORTED', false);
+}
+
+if (!function_exists('rfc3986_urlencode')) {
+ /**
+ * Encodes a URL using RFC 3986.
+ *
+ * PHP's rfc3986_urlencode function encodes a URL using RFC 1738 for PHP versions
+ * prior to 5.3. RFC 1738 has been
+ * updated by RFC 3986, which change the list of characters which needs to be
+ * encoded.
+ *
+ * Strictly correct encoding is required for various purposes, such as OAuth
+ * signature base strings.
+ *
+ * @param string $s the URL to encode
+ * @return string the encoded URL
+ */
+ function rfc3986_urlencode($s) {
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ return rawurlencode($s);
+ } else {
+ return str_replace('%7E', '~', rawurlencode($s));
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/simpleid/www/page.inc.php b/simpleid/www/page.inc.php
new file mode 100644
index 0000000..c1c5ef6
--- /dev/null
+++ b/simpleid/www/page.inc.php
@@ -0,0 +1,331 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Functions for displaying various pages in SimpleID.
+ *
+ * @package simpleid
+ * @filesource
+ * @since 0.7
+ */
+
+/**
+ * Displays the dashboard page.
+ */
+function page_dashboard() {
+ global $user;
+ global $xtpl;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ if ($user == NULL) {
+ user_login_form('');
+ return;
+ }
+
+ user_header();
+ page_nav();
+
+ $blocks = _page_welcome_block();
+
+ $blocks = array_merge($blocks, _page_dashboard_otp_block(), extension_invoke_all('page_dashboard'));
+ $blocks = array_map('page_render_block', $blocks);
+ $xtpl->assign('blocks', implode($blocks));
+ $xtpl->parse('main.blocks');
+
+ $xtpl->assign('title', t('Dashboard'));
+ $xtpl->parse('main');
+ $xtpl->out('main');
+
+}
+
+/**
+ * Displays the profile page.
+ */
+function page_profile() {
+ global $user;
+ global $xtpl;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ if ($user == NULL) {
+ user_login_form('my/profile');
+ return;
+ }
+
+ user_header();
+ page_nav();
+
+ $blocks = _page_discovery_block();
+
+ $blocks = array_merge($blocks, _user_page_profile(), extension_invoke_all('page_profile'));
+ $blocks = array_map('page_render_block', $blocks);
+ $xtpl->assign('blocks', implode($blocks));
+ $xtpl->parse('main.blocks');
+
+ $xtpl->assign(array('js_locale_label' => 'code', 'js_locale_text' => addslashes(t('<em>You need to set at least one of OpenID 1.x or OpenID 2 to generate the code.</em>'))));
+ $xtpl->parse('main.js_locale');
+
+ $xtpl->assign('javascript', '<script src="' . get_base_path() . 'html/page-profile.js" type="text/javascript"></script>');
+ $xtpl->assign('title', t('My Profile'));
+ $xtpl->parse('main');
+ $xtpl->out('main');
+}
+
+/**
+ * Returns the user's home page.
+ */
+function page_sites() {
+ global $user;
+ global $xtpl;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ if ($user == NULL) {
+ user_login_form('my/sites');
+ return;
+ }
+
+ user_header();
+ page_nav();
+
+ if (isset($user['rp'])) {
+ $user_rps =& $user['rp'];
+ } else {
+ $user_rps = array();
+ }
+
+ if (isset($_POST['tk'])) {
+ if (!validate_form_token($_POST['tk'], 'autorelease')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ } else {
+ if (isset($_POST['autorelease'])) {
+ foreach ($_POST['autorelease'] as $realm => $autorelease) {
+ if (isset($user_rps[$realm])) {
+ $user_rps[$realm]['auto_release'] = ($autorelease) ? 1 : 0;
+ }
+ }
+ }
+
+ if (isset($_POST['remove'])) {
+ foreach ($_POST['remove'] as $realm => $autorelease) {
+ if (isset($user_rps[$realm])) {
+ unset($user_rps[$realm]);
+ }
+ }
+ }
+
+ if (isset($_POST['update-all'])) {
+ foreach ($user_rps as $realm => $values) {
+ $user_rps[$realm]['auto_release'] = (isset($_POST['autorelease'][$realm]) && $_POST['autorelease'][$realm]) ? 1 : 0;
+ }
+ }
+
+ user_save($user);
+
+ set_message(t('Your preferences have been saved.'));
+ }
+ }
+
+ if ($user_rps) {
+ uksort($user_rps, '_page_sites_sort');
+ foreach ($user_rps as $realm => $rp) {
+ $xtpl->assign('realm_name', preg_replace('@^https?://(www\.|\*\.)?@', '<span class="url-elide">$0</span>', htmlspecialchars($rp['realm'], ENT_QUOTES, 'UTF-8')));
+ $xtpl->assign('realm', htmlspecialchars($rp['realm'], ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('last_time', htmlspecialchars($rp['last_time'], ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('last_time_formatted', htmlspecialchars(strftime(SIMPLEID_DATE_TIME_FORMAT, $rp['last_time']), ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('auto_release', (isset($rp['auto_release']) && $rp['auto_release']) ? 'checked="checked"' : '');
+
+ if (SIMPLEID_VERIFY_RETURN_URL_USING_REALM) {
+ // $rp_info would usually expire by now, so we allow for stale results to be retrieved to improve performance
+ $rp_info = simpleid_get_rp_info($realm, TRUE);
+ if (!isset($rp_info['return_to_verified']) || !$rp_info['return_to_verified']) $xtpl->assign('realm_class', 'return-to-suspect');
+ }
+
+ $xtpl->parse('main.sites.realm');
+ }
+ }
+
+ if (!$user_rps || (count($user_rps) == 0)) {
+ $xtpl->assign('disabled', 'disabled="disabled"');
+ }
+
+ $xtpl->assign('token', get_form_token('autorelease'));
+
+ $xtpl->assign('realm_label', t('Site'));
+ $xtpl->assign('last_time_label', t('Last access'));
+ $xtpl->assign('auto_release_label', t('Automatic'));
+ $xtpl->assign('remove_label', t('Remove'));
+ $xtpl->assign('submit_button', t('Submit'));
+
+ $xtpl->parse('main.sites');
+
+ $xtpl->assign(array('js_locale_label' => 'openid_suspect', 'js_locale_text' => addslashes(t('This web site has not confirmed its identity and might be fraudulent.')) . '\n\n' . addslashes(t('Are you sure you wish to automatically send your information to this site for any future requests?'))));
+ $xtpl->parse('main.js_locale');
+
+ $xtpl->assign('title', t('My Sites'));
+ $xtpl->assign('javascript', '<script src="' . get_base_path() . 'html/openid-consent.js" type="text/javascript"></script>');
+ $xtpl->parse('main');
+ $xtpl->out('main');
+}
+
+/**
+ * A custom sort function for realms. This strips out the following:
+ *
+ * - http://
+ * - https://
+ * - www.
+ * - *.
+ *
+ * @param string $a
+ * @param string $b
+ * @return int
+ */
+function _page_sites_sort($a, $b) {
+ $a = preg_replace('@^https?://(www\.|\*\.)?@', '', $a);
+ $b = preg_replace('@^https?://(www\.|\*\.)?@', '', $b);
+ return strcasecmp($a, $b);
+}
+
+/**
+ * Set up the navigation section in the header
+ */
+function page_nav() {
+ global $user;
+ global $xtpl;
+
+ $xtpl->assign('nav_base', trim(simpleid_url(' ', '', true)));
+
+ $xtpl->assign('nav_dashboard_label', t('Dashboard'));
+ $xtpl->assign('nav_profile_label', t('My Profile'));
+ $xtpl->assign('nav_sites_label', t('My Sites'));
+
+ $xtpl->parse('main.nav_toggle');
+ $xtpl->parse('main.nav');
+}
+
+/**
+ * Renders a particular block.
+ *
+ * @param array $block the block to render
+ * @return string the HTML of the rendered block
+ */
+function page_render_block($block) {
+ static $xtpl_block;
+
+ if (!$xtpl_block) $xtpl_block = new XTemplate('html/block.xtpl');
+
+ $xtpl_block->reset('block');
+ $xtpl_block->assign('id', $block['id']);
+ $xtpl_block->assign('title', $block['title']);
+ $xtpl_block->assign('content', $block['content']);
+
+ if (isset($block['links'])) {
+ $xtpl_block->assign('links', $block['links']);
+ $xtpl_block->parse('block.links');
+ }
+
+ $xtpl_block->parse('block');
+ return $xtpl_block->text('block');
+}
+
+/**
+ * Returns the welcome block.
+ *
+ * @return array the welcome block
+ */
+function _page_welcome_block() {
+ global $user;
+
+ return array(array(
+ 'id' => 'welcome',
+ 'title' => t('Welcome'),
+ 'content' => t('You are logged in as %uid (%identity).', array('%uid' => $user['uid'], '%identity' => $user['identity']))
+ ));
+}
+
+/**
+ * Returns the dashboard OTP block.
+ *
+ * @return array the dashboard OTP block
+ */
+function _page_dashboard_otp_block() {
+ global $user;
+
+ $base_path = get_base_path();
+
+ $html = '<p>' . t('Login verification adds an extra layer of protection to your account. When enabled, you will need to enter an additional security code whenever you log into SimpleID.') . '</p>';
+
+ if (isset($user['otp'])) {
+ $html .= '<p>' . t('Login verification is <strong>enabled</strong>.') . '</p>';
+ $html .= '<form action="' . $base_path . 'index.php" method="post" enctype="application/x-www-form-urlencoded"><input type="hidden" name="tk" value="'. get_form_token('dashboard_otp') . '"/>';
+ $html .= '<input type="hidden" name="q" value="otp"/><input type="submit" name="op" value="' . t('Disable') . '" /></form>';
+ } else {
+ $html .= '<p>' . t('Login verification is <strong>disabled</strong>. To enable login verification, click the button below.') . '</p>';
+ $html .= '<form action="' . $base_path . 'index.php" method="post" enctype="application/x-www-form-urlencoded"><input type="hidden" name="tk" value="'. get_form_token('dashboard_otp') . '"/>';
+ $html .= '<input type="hidden" name="q" value="otp"/><input type="submit" name="op" value="' . t('Enable') . '" /></form>';
+ }
+
+ return array(array(
+ 'id' => 'otp',
+ 'title' => t('Login Verification'),
+ 'content' => $html
+ ));
+}
+
+/**
+ * Returns a block containing discovery information.
+ *
+ * @return array the discovery block
+ */
+function _page_discovery_block() {
+ global $user;
+
+ $html = "<h3>" . t('<link> tags') . "</h3>";
+
+ $html .= "<div><label><input type=\"checkbox\" name=\"openid1\" value=\"1\" id=\"discovery-openid1\" class=\"discovery-checkbox\" />" . t('OpenID 1.x') . "</label>";
+ $html .= "<label><input type=\"checkbox\" name=\"openid2\" value=\"1\" id=\"discovery-openid2\" class=\"discovery-checkbox\" />" . t('OpenID 2.0') . "</label>";
+ $html .= "<label><input type=\"checkbox\" name=\"local-id\" value=\"1\" id=\"discovery-local-id\" class=\"discovery-checkbox\" />" . t('Claim a different identifier') . "</label></div>";
+ $html .= "<pre id=\"discovery-link-tags\">";
+ $html .= "</pre>";
+ $html .= "<ul id=\"discovery-templates\"><li class=\"openid1\"><link rel="openid.server" href="" . htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8') . "" /></li>\n";
+ $html .= "<li class=\"openid2\"><link rel="openid2.provider" href="" . htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8') ."" /></li>\n";
+ $html .= "<li class=\"openid1-local-id\"><link rel="openid.delegate" href="" . htmlspecialchars($user['identity'], ENT_QUOTES, 'UTF-8') . "" /></li>\n";
+ $html .= "<li class=\"openid2-local-id\"><link rel="openid2.local_id" href="" . htmlspecialchars($user['identity'], ENT_QUOTES, 'UTF-8') ."" /></li></ul>\n";
+
+ $html .= "<h3>" . t('YADIS') . "</h3>";
+ $html .= "<ol><li>" . t('Write your own or <a href="!url">download</a> your YADIS document', array('!url' => simpleid_url('xrds/'. $user['uid'], '', true))) . "</li>";
+ $html .= "<li><div>" . t('Add HTTP headers or <meta> tag, e.g.:') . "<div><pre><meta http-equiv="X-XRDS-Location" content="" . htmlspecialchars(simpleid_url('xrds/'. $user['uid']), ENT_QUOTES, 'UTF-8') . "" /></pre>";
+ $html .= "</li></ol>";
+
+ return array(array(
+ 'id' => 'discovery',
+ 'title' => t('Claim your Identifier'),
+ 'content' => $html,
+ 'links' => '<a href="http://simpleid.org/docs/1/identity-claim/">More information</a>'
+ ));
+}
+?>
diff --git a/simpleid/www/random.inc.php b/simpleid/www/random.inc.php
new file mode 100644
index 0000000..efee496
--- /dev/null
+++ b/simpleid/www/random.inc.php
@@ -0,0 +1,107 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2010
+ *
+ * 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$
+ */
+
+/**
+ * Functions related to generating random bits and unique values.
+ *
+ * @package simpleid
+ * @since 0.8
+ * @filesource
+ */
+
+if (!defined('SIMPLEID_RAND_SOURCE')) {
+ /**
+ * The source of random bits. On Unix-like systems, this could be /dev/random
+ * or /dev/urandom
+ */
+ define('SIMPLEID_RAND_SOURCE', '/dev/urandom');
+}
+
+/**
+ * Obtains a number of random bytes. This function uses an entropy source specified
+ * in SIMPLEID_RAND_SOURCE. If SIMPLEID_RAND_SOURCE is not available, the mt_rand()
+ * PHP function is used
+ * If the native PHP random_bytes function exists (PHP 7+), this function won't be defined here.
+ *
+ * @param int $num_bytes the number of bytes to generate
+ * @return string a string containing random bytes
+ */
+if(!function_exists('random_bytes')) {
+ function random_bytes($num_bytes) {
+ static $f = null;
+ $bytes = '';
+ if ($f === null) {
+ if (SIMPLEID_RAND_SOURCE === null) {
+ $f = FALSE;
+ } else {
+ $f = @fopen(SIMPLEID_RAND_SOURCE, "r");
+ }
+ }
+ if ($f === FALSE) {
+ $bytes = '';
+ for ($i = 0; $i < $num_bytes; $i += 4) {
+ $bytes .= pack('L', mt_rand());
+ }
+ $bytes = substr($bytes, 0, $num_bytes);
+ } else {
+ $bytes = fread($f, $num_bytes);
+ }
+ return $bytes;
+ }
+}
+
+/**
+ * Obtains a random string of a specified number of bytes of entropy.
+ *
+ * The function calls the {@link random_bytes()} function with the specified
+ * number of bytes, then converts to a string containing only alphanumeric
+ * characters (case sensitive), plus the characters ., _ and -.
+ *
+ * The conversion method is based on the Base64 encoding. However, non-standard
+ * characters are used so that users are not confused and attempt to decode
+ * the returned string.
+ *
+ * @param int $num_bytes the approximate number of bytes of entropy in the
+ * random string
+ * @return string the random string
+ */
+function random_secret($num_bytes = 32) {
+ return strtr(base64_encode(random_bytes($num_bytes)),
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
+ '-_.9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA');
+}
+
+/**
+ * Generates a relatively unique identifier which can be used as, among other things,
+ * an OpenID association handle or an OAuth client identifier. The identifier
+ * returned is at least 24 characters long and contains only hexadecimal characters.
+ *
+ * Note that the identifier returned is not cryptographically secure.
+ *
+ * @return string a relatively unique identifier
+ */
+function random_id() {
+ $timeofday = gettimeofday();
+ return vsprintf('%08x%08x', $timeofday) . bin2hex(random_bytes(4));
+}
+?>
diff --git a/simpleid/www/simpleweb.inc.php b/simpleid/www/simpleweb.inc.php
new file mode 100644
index 0000000..48cdbc6
--- /dev/null
+++ b/simpleid/www/simpleweb.inc.php
@@ -0,0 +1,157 @@
+<?php
+/*
+ * SimpleWeb
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * Simpleweb is a minimalist web framework. It is similar to {@link http://webpy.org web.py},
+ * but in PHP.
+ *
+ * The key to Simpleweb is the <i>route array</i>. The route array is an array that maps
+ * URLs (called <i>patterns</i>) to PHP functions or methods (called <i>routes</i>).
+ *
+ * Patterns are regular expressions, which are tested against the URL one at a time.
+ * Subpatterns (i.e. patterns within parentheses) are then passed on as arguments
+ * to the route.
+ *
+ * Routes are either functions, static methods or object methods. A function is
+ * denoted by the function name. A static method is denoted by the class name,
+ * followed by :: then the method name. An object method is denoted by the class
+ * name, followed by -> then the method name. An instance of the class will be
+ * created before an object method is called.
+ *
+ * An example of a routes array is given below:
+ *
+ * <code>
+ * <?php
+ * $routes = array(
+ * 'a' => 'function1',
+ * 'b/(.+)' => 'function2', // arguments
+ * 'c' => 'ClassA::method', // static method
+ * 'd' => 'ClassB->method', // object method
+ * );
+ * ?>
+ * </code>
+ *
+ * Once the route array is populated, the {@link simpleweb_run()} function
+ * is then called to handle the URL.
+ *
+ * @package simpleweb
+ * @since 0.7
+ */
+
+/**
+ * Handles a supplied request, based on a set of routes.
+ *
+ * @param array $routes the routes array, as described in {@link simpleweb.inc this page}
+ * @param string $request_path the request path against which the routes are applied. If
+ * NULL, then the request URI supplied by the web server will be used.
+ * @param string $not_found_route the default route if none of the patterns match. If
+ * NULL, then an HTTP 404 error is raised
+ * @return mixed the result from calling the route.
+ *
+ */
+
+function simpleweb_run($routes, $request_path = NULL, $not_found_route = NULL) {
+ if ($request_path == NULL) {
+ // We take the request path from the request URI
+ $request_path = $_SERVER['REQUEST_URI'];
+
+ // Strip off all parts to the script file name. Sadly, PHP is historically
+ // buggy in its treatment of SCRIPT_NAME, so we need to try a few methods
+ // to strip them
+ $script_name = basename($_SERVER['SCRIPT_NAME']);
+ $script_dir = dirname($_SERVER['SCRIPT_NAME']);
+
+ if (strpos($request_path, $script_name) !== false) {
+ $request_path = substr($request_path, strpos($request_path, $script_name) + strlen($script_name));
+ } elseif ($script_dir != '/') {
+ $request_path = str_replace($script_dir, '', $request_path);
+ }
+
+ $request_path = trim($request_path, '/');
+ }
+
+ // Strip off GET parameters when passed in SAPI CGI mode
+ $request_path = strtok($request_path, '?');
+
+ foreach ($routes as $pattern => $route) {
+
+ if (!isset($route)) continue;
+ $regex = '#^' . trim($pattern, '/') . '$#i';
+
+ if (!preg_match($regex, $request_path, $args) > 0) continue;
+
+ $args = (count($args) > 1) ? array_slice($args, 1) : array();
+ return _simpleweb_invoke($route, $args);
+ }
+
+ if ($not_found_route) return _simpleweb_invoke($not_found_route, array($request_path));
+
+ _simpleweb_not_found();
+}
+
+/**
+ * Invokes a route.
+ *
+ * @param string $route the route
+ * @param array $args the arguments
+ * @return mixed the result from calling the route.
+ */
+function _simpleweb_invoke($route, $args = array()) {
+ if (strpos($route, '::') !== false) {
+ list($class, $method) = split($route, '::', 2);
+ return call_user_func_array(array($class, $method), $args);
+ } elseif(strpos($route, '->') !== false) {
+ list($class, $method) = split($route, '->', 2);
+ $object &= new $class;
+ return call_user_func_array(array($object, $method), $args);
+ } else {
+ return call_user_func_array($route, $args);
+ }
+}
+
+/**
+ * Displays a HTTP 404 Not Found error and exits.
+ */
+function _simpleweb_not_found() {
+ switch ($_SERVER['REDIRECT_STATUS']) {
+ case '403':
+ $status = '403 Forbidden';
+ break;
+ case '404':
+ default:
+ $status = '404 Not Found';
+ break;
+ }
+
+ if (substr(PHP_SAPI, 0, 3) === 'cgi') {
+ header('Status: ' . $status);
+ } else {
+ header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status);
+ }
+ header('Content-Type: text/html');
+
+ print '<!doctype html><html><head><title>' . $status . '</title></head><body><h1>' . $status . '</h1></body></html>';
+
+ exit;
+}
+?>
diff --git a/simpleid/www/upgrade.php b/simpleid/www/upgrade.php
new file mode 100644
index 0000000..a4d5940
--- /dev/null
+++ b/simpleid/www/upgrade.php
@@ -0,0 +1,466 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2009
+ *
+ * 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$
+ */
+
+/**
+ * SimpleID upgrade script.
+ *
+ * This script performs various upgrades to SimpleID's storage backend, which
+ * are required for different versions of SimpleID.
+ *
+ * @package simpleid
+ * @since 0.7
+ * @filesource
+ */
+
+/**
+ * Access control for this script.
+ *
+ * If you are upgrading your SimpleID installation using the upgrade.php script,
+ * and you are not logged in as an administrator, you will need to modify the access
+ * check statement below.
+ *
+ * Change the TRUE to a FALSE to disable the access
+ * check. After finishing the upgrade, be sure to open this file again
+ * and change the FALSE back to a TRUE.
+ *
+ * @global bool $upgrade_access_check
+ */
+$upgrade_access_check = TRUE;
+
+/* ----- Do not modify anything following this line ------------------------- */
+
+include_once "version.inc.php";
+include_once "locale.inc.php";
+if (file_exists("config.php")) {
+ include_once "config.php";
+} elseif (file_exists("config.inc")) {
+ include_once "config.inc";
+ define('UPGRADE_LEGACY_CONFIG_INC', TRUE);
+} else {
+ die(t('No configuration file found. See the <a href="!url">manual</a> for instructions on how to set up a configuration file.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+}
+include_once "config.default.php";
+include_once "log.inc.php";
+include_once "common.inc.php";
+include_once "simpleweb.inc.php";
+include_once "openid.inc.php";
+include_once "user.inc.php";
+include_once "cache.inc.php";
+include_once SIMPLEID_STORE . ".store.php";
+include "lib/xtemplate.class.php";
+
+define('CACHE_DIR', SIMPLEID_CACHE_DIR);
+
+define('PRE_0_7_0_VERSION', '0.6.0 or earlier');
+
+/**
+ * This variable holds the upgrade functions for each version of SimpleID
+ *
+ * @global array $upgrade_functions
+ */
+$upgrade_functions = array(
+ '0.9.0' => array('upgrade_config_inc_to_php', 'upgrade_delete_token'),
+ '0.7.0' => array('upgrade_rp_to_store', 'upgrade_token_to_store')
+);
+
+
+/**
+ * This variable holds an instance of the XTemplate engine.
+ *
+ * @global object $xtpl
+ */
+$xtpl = NULL;
+
+/**
+ * This variable holds the combined $_GET and $_POST superglobal arrays.
+ *
+ * @global array $GETPOST
+ */
+$GETPOST = array_merge($_GET, $_POST);
+
+upgrade_start();
+
+/**
+ * Entry point for SimpleID upgrade script.
+ *
+ * @see user_init()
+ */
+function upgrade_start() {
+ global $xtpl, $GETPOST;
+
+ locale_init(SIMPLEID_LOCALE);
+
+ $xtpl = new XTemplate('html/template.xtpl');
+ $xtpl->assign('version', SIMPLEID_VERSION);
+ $xtpl->assign('base_path', get_base_path());
+ $xtpl->assign('css', '@import url(' . get_base_path() . 'html/upgrade.css);');
+ $xtpl->assign('footer_doc', t('Documentation'));
+ $xtpl->assign('footer_support', t('Support'));
+
+ if (!is_dir(SIMPLEID_IDENTITIES_DIR)) {
+ indirect_fatal_error(t('Identities directory not found. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ }
+
+ if (!is_dir(SIMPLEID_CACHE_DIR) || !is_writeable(SIMPLEID_CACHE_DIR)) {
+ indirect_fatal_error(t('Cache directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ }
+
+ if (!is_dir(SIMPLEID_STORE_DIR) || !is_writeable(SIMPLEID_STORE_DIR)) {
+ indirect_fatal_error(t('Store directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ }
+
+ if ((@ini_get('register_globals') === 1) || (@ini_get('register_globals') === '1') || (strtolower(@ini_get('register_globals')) == 'on')) {
+ indirect_fatal_error(t('register_globals is enabled in PHP configuration, which is not supported by SimpleID. See the <a href="!url">manual</a> for further information.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+
+ if (!bignum_loaded()) {
+ log_fatal('gmp/bcmath PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'gmp/bcmath', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('preg_match')) {
+ log_fatal('pcre PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'pcre', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('session_start')) {
+ log_fatal('session PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'session', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('xml_parser_create_ns')) {
+ log_fatal('xml PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'xml', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (!function_exists('hash')) {
+ log_fatal('hash PHP extension not loaded.');
+ indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'hash', '!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+ if (is_numeric(@ini_get('suhosin.get.max_value_length')) && (@ini_get('suhosin.get.max_value_length') < 1024)) {
+ log_fatal('suhosin.get.max_value_length < 1024');
+ indirect_fatal_error(t('suhosin.get.max_value_length is less than 1024, which will lead to problems. See the <a href="!url">manual</a> for further information on system requirements.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/')));
+ }
+
+ $q = (isset($GETPOST['q'])) ? $GETPOST['q'] : '';
+ $q = explode('/', $q);
+
+ extension_init();
+ user_init(NULL);
+ upgrade_user_init();
+
+ $routes = array(
+ 'upgrade-selection' => 'upgrade_selection',
+ 'upgrade-apply' => 'upgrade_apply',
+ '.*' => 'upgrade_info'
+ );
+
+ simpleweb_run($routes, implode('/', $q));
+}
+
+/**
+ * Displays the upgrade info page.
+ */
+function upgrade_info() {
+ global $xtpl;
+
+ $xtpl->assign('token', get_form_token('upgrade_info'));
+
+ $xtpl->assign('intro', t('Use this script to update your installation whenever you upgrade to a new version of SimpleID.'));
+ $xtpl->assign('simpleid_docs', t('For more detailed information, see the <a href="!url">SimpleID documentation</a>.', array('!url' => 'http://simpleid.org/docs/1/upgrading/')));
+ $xtpl->assign('step1', t('<strong>Back up your installation</strong>. This process will change various files within your SimpleID installation and in case of emergency you may need to revert to a backup.'));
+ $xtpl->assign('step2', t('Install your new files in the appropriate location, as described in the <a href="!url">SimpleID documentation</a>.', array('!url' => 'http://simpleid.org/docs/1/installing/')));
+ $xtpl->assign('click_continue', t('When you have performed the steps above, click <strong>Continue</strong>.'));
+ $xtpl->assign('continue_button', t('Continue'));
+
+ $xtpl->parse('main.upgrade_info');
+
+ $xtpl->assign('title', t('Upgrade'));
+ $xtpl->parse('main');
+
+ $xtpl->out('main');
+}
+
+/**
+ * Detects the current installed version of SimpleID, selects the individual upgrade
+ * functions applicable to this upgrade and displays the upgrade
+ * selection page.
+ */
+function upgrade_selection() {
+ global $xtpl, $upgrade_access_check;
+
+ cache_expire(array('upgrade' => 0));
+
+ if (!validate_form_token($_POST['tk'], 'upgrade_info')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ upgrade_info();
+ return;
+ }
+
+ $functions = upgrade_get_functions();
+
+ if (count($functions) == 0) {
+ if (!$upgrade_access_check) {
+ $xtpl->assign('edit_upgrade_php', t('Remember to edit upgrade.php to check <code>$upgrade_access_check</code> back to <code>FALSE</code>.'));
+ $xtpl->parse('main.selection.selection_complete.upgrade_access_check');
+ }
+
+ $xtpl->assign('script_complete', t('Your SimpleID installation is up-to-date. This script is complete.'));
+
+ $xtpl->parse('main.upgrade_selection.selection_complete');
+ } else {
+ $handle = random_id();
+ cache_set('upgrade', $handle, $functions);
+
+ $xtpl->assign('handle', $handle);
+ $xtpl->assign('token', get_form_token('upgrade_selection'));
+
+ $xtpl->assign('click_continue', t('Click <strong>Continue</strong> to proceed with the upgrade.'));
+ $xtpl->assign('continue_button', t('Continue'));
+
+ $xtpl->parse('main.upgrade_selection.selection_continue');
+ }
+
+ $xtpl->assign('original_version', upgrade_get_version());
+ $xtpl->assign('this_version', SIMPLEID_VERSION);
+
+ $xtpl->assign('version_detected', t('The version of SimpleID you are updating from has been automatically detected.'));
+ $xtpl->assign('original_version_label', t('Original version'));
+ $xtpl->assign('this_version_label', t('Upgrade version'));
+
+ $xtpl->parse('main.upgrade_selection');
+
+ $xtpl->assign('title', t('Upgrade'));
+ $xtpl->parse('main');
+
+ $xtpl->out('main');
+}
+
+/**
+ * Applies the upgrade.
+ */
+function upgrade_apply() {
+ global $xtpl, $upgrade_access_check;
+
+ if (!validate_form_token($_POST['tk'], 'upgrade_selection')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ upgrade_selection();
+ return;
+ }
+
+ $results = '';
+ $functions = cache_get('upgrade', $_POST['handle']);
+
+ foreach ($functions as $function) {
+ $results .= call_user_func($function);
+ }
+
+ if (!$upgrade_access_check) {
+ $xtpl->assign('edit_upgrade_php', t('Remember to edit upgrade.php to check <code>$upgrade_access_check</code> back to <code>TRUE</code>.'));
+ $xtpl->parse('main.upgrade_results.upgrade_access_check');
+ }
+ $xtpl->assign('results', $results);
+
+ $xtpl->assign('upgrade_complete', t('Your SimpleID installation has been upgraded. Please check the results below for any errors.'));
+
+ $xtpl->parse('main.upgrade_results');
+
+ cache_expire(array('upgrade' => 0));
+
+ $xtpl->assign('title', t('Upgrade'));
+ $xtpl->parse('main');
+
+ $xtpl->out('main');
+}
+
+/**
+ * Detects the current installed version of SimpleID
+ *
+ * The current installed version of SimpleID is taken from the {@link store_get() version}
+ * application setting. This setting is only available for versions 0.7 or later, so
+ * if it is absent we can assume it's prior to version 0.7.
+ *
+ * @return string the detected version, or the string '0.6.0 or earlier'
+ */
+function upgrade_get_version() {
+ return store_get('version', '0.6.0 or earlier');
+}
+
+/**
+ * Sets the current version of SimpleID.
+ *
+ * This function sets the version application setting via {@link store_get()}.
+ * A specific version can be specified, or it can be taken from {@link SIMPLEID_VERSION}.
+ *
+ * @param string $version the version to set
+ */
+function upgrade_set_version($version = NULL) {
+ if ($version == NULL) $version = SIMPLEID_VERSION;
+ store_set('version', $version);
+}
+
+/**
+ * Selects the upgrade functions applicable for this upgrade.
+ *
+ * The upgrade functions are specified by the {@link $upgrade_functions}
+ * variable. This variable is an associative array containing version numbers
+ * as keys and an array of upgrade function names as values. This function
+ * merges all the upgrade function names of the version between the current
+ * installed version and the upgraded version.
+ *
+ * @param string $version the version of SimpleID to upgrade from, calls
+ * {@link upgrade_get_version()} if not specified
+ * @return array an array of strings, containing the list of upgrade functions
+ * to call. The functions should be called in the same order as they appear
+ * in this array
+ *
+ */
+function upgrade_get_functions($version = NULL) {
+ global $upgrade_functions;
+
+ if ($version == NULL) $version = upgrade_get_version();
+ $functions = array();
+
+ uksort($upgrade_functions, '_upgrade_version_reverse_sort');
+
+ foreach ($upgrade_functions as $upgrade_version => $upgrades) {
+ if (version_compare($version, $upgrade_version, '<')) {
+ $functions = array_merge($functions, $upgrades);
+ }
+ }
+
+ if (version_compare($version, SIMPLEID_VERSION, '<')) $functions[] = 'upgrade_set_version';
+
+ return $functions;
+}
+
+/**
+ * Callback function for uksort() to reverse sort version numbers.
+ *
+ * @param string $a
+ * @param string $b
+ * @return int
+ */
+function _upgrade_version_reverse_sort($a, $b) {
+ return -version_compare($a, $b);
+}
+
+/**
+ * Determines whether the current user has permission to run this script.
+ *
+ * A user has permission to run this script if:
+ *
+ * - administrator=1 appears in the user's identity file; or
+ * - {@link $upgrade_access_check} is false
+ *
+ * If the user does not have permission, {@link upgade_access_denied()} is called
+ */
+function upgrade_user_init() {
+ global $user, $upgrade_access_check;
+
+ if ($upgrade_access_check) {
+ if (($user == NULL) || ($user['administrator'] != 1)) upgrade_access_denied();
+ }
+}
+
+/**
+ * Displays a page notifying the user that he or she does not have permission to
+ * run the upgrade script.
+ */
+function upgrade_access_denied() {
+ global $xtpl;
+
+ $xtpl->assign('login_required', t('Access denied. You are not authorised to access this page. Please <a href="index.php?q=login">log in</a> as an administrator (a user whose identity file includes the line <code>administrator=1</code>).'));
+ $xtpl->assign('edit_upgrade_php', t('If you cannot log in, you will have to edit <code>upgrade.php</code> to bypass this access check. To do this:'));
+ $xtpl->assign('edit_upgrade_php1', t('With a text editor find the upgrade.php file.'));
+ $xtpl->assign('edit_upgrade_php2', t('There is a line inside your upgrade.php file that says <code>$upgrade_access_check = TRUE;</code>. Change it to <code>$upgrade_access_check = FALSE;</code>.'));
+ $xtpl->assign('edit_upgrade_php3', t('As soon as the upgrade.php script is done, you must change the file back to its original form with <code>$upgrade_access_check = TRUE;</code>.'));
+ $xtpl->assign('edit_upgrade_php4', t('To avoid having this problem in future, remember to log in to SimpleID as an administrator before you run this script.'));
+ $xtpl->assign('simpleid_docs', t('For more detailed information, see the <a href="!url">SimpleID documentation</a>.', array('!url' => 'http://simpleid.org/docs/1/upgrading/')));
+
+ $xtpl->parse('main.upgrade_access_denied');
+
+ $xtpl->assign('title', t('Access Denied'));
+ $xtpl->parse('main');
+
+ $xtpl->out('main');
+ exit;
+}
+
+/* ------------------------------------------------------------------------------------------------------- */
+
+/**
+ * Moves the user's site preferences from the cache to the store.
+ *
+ * @since 0.7
+ */
+function upgrade_rp_to_store() {
+ $dir = opendir(SIMPLEID_IDENTITIES_DIR);
+
+ while (($file = readdir($dir)) !== false) {
+ $filename = SIMPLEID_IDENTITIES_DIR . '/' . $file;
+
+ if ((filetype($filename) != "file") || (!preg_match('/^(.+)\.identity$/', $file, $matches))) continue;
+
+ $uid = $matches[1];
+
+ $user = user_load($uid);
+ $rp = cache_get('rp', $uid);
+ if ($rp != NULL) {
+ $user['rp'] = $rp;
+ user_save($user);
+ cache_delete('rp', $uid);
+ }
+ }
+}
+
+/**
+ * Moves the site token from the cache to the store.
+ *
+ * @since 0.7
+ */
+function upgrade_token_to_store() {
+ $site_token = cache_get('token', SIMPLEID_BASE_URL);
+
+ if ($site_token != NULL) {
+ store_set('site-token', $site_token);
+ cache_delete('token', SIMPLEID_BASE_URL);
+ }
+}
+
+/**
+ * Checks that config.inc has been renamed to config.php
+ *
+ * @since 0.9
+ */
+function upgrade_config_inc_to_php() {
+ if (defined('UPGRADE_LEGACY_CONFIG_INC')) {
+ return '<p>You will need to rename <code>config.inc</code> to <code>config.php</code>.</p>';
+ }
+}
+
+/**
+ * Deletes the site-token setting for a more secure version
+ *
+ * @since 0.9
+ */
+function upgrade_delete_token() {
+ store_del('site-token');
+}
+?>
diff --git a/simpleid/www/user.inc.php b/simpleid/www/user.inc.php
new file mode 100644
index 0000000..d10b5c0
--- /dev/null
+++ b/simpleid/www/user.inc.php
@@ -0,0 +1,1065 @@
+<?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$
+ */
+
+/**
+ * User functions.
+ *
+ * @package simpleid
+ * @filesource
+ */
+
+/**
+ * The time the nonce used in the login process will last.
+ */
+define('SIMPLEID_LOGIN_NONCE_EXPIRES_IN', 3600);
+
+/**
+ * The time (in seconds) the auto login cookie will last. This is currently
+ * set as 2 weeks.
+ */
+define('SIMPLEID_USER_AUTOLOGIN_EXPIRES_IN', 1209600);
+
+/**
+ * This variable holds data on the currently logged-in user. If the user is
+ * not logged in, this variable is NULL.
+ *
+ * @global array $user
+ */
+$user = NULL;
+
+/**
+ * Initialises the user system. Loads data for the currently logged-in user,
+ * if any.
+ *
+ * @param string $q the SimpleID command, if any
+ */
+function user_init($q = NULL) {
+ global $user;
+ global $xtpl;
+
+ log_debug('user_init');
+
+ $user = NULL;
+
+ // session_name() has to be called before session_set_cookie_params()
+ session_name(simpleid_cookie_name('sess'));
+
+ // Note the last parameter (httponly) requires PHP 5.2
+ session_set_cookie_params(0, get_base_path(), ini_get('session.cookie_domain'), false, true);
+ session_start();
+
+ if (isset($_SESSION['user']) && (cache_get('user', $_SESSION['user']) == session_id())) {
+ $user = user_load($_SESSION['user']);
+
+ // If user has just been actively been authenticated in the previous request, then we
+ // make it as actively authenticated in this request.
+ if (isset($_SESSION['user_auth_active']) && $_SESSION['user_auth_active']) {
+ $user['auth_active'] = true;
+ unset($_SESSION['user_auth_active']);
+ }
+ } else {
+ if (($q == 'login') || ($q == 'logout')) return;
+ user_auto_login();
+ }
+}
+
+/**
+ * Attempts to automatically login using credentials presented by the user agent.
+ *
+ * The user agent may present various credentials as part of its request. These
+ * may include cookies and SSL client certificates. This function calls the
+ * {@link hook_user_auto_login()} hook of enabled extensions to see if any
+ * of these credentials can be used to automatically login a user.
+ */
+function user_auto_login() {
+ global $simpleid_extensions;
+
+ $extensions = $simpleid_extensions;
+
+ if (!in_array('user_cookieauth', $extensions)) $extensions[] = 'user_cookieauth';
+
+ foreach ($extensions as $extension) {
+ $test_user = extension_invoke($extension, 'user_auto_login');
+ if ($test_user != NULL) {
+ _user_login($test_user);
+ }
+ }
+}
+
+/**
+ * Loads user data for a specified user name.
+ *
+ * @param string $uid the name of the user to load
+ * @return mixed data for the specified user, or NULL if the user name does not
+ * exist
+ * @see user_load_from_identity()
+ */
+function user_load($uid) {
+ if (store_user_exists($uid)) {
+ $user = store_user_load($uid);
+ $user["uid"] = $uid;
+
+ if (isset($user["identity"])) {
+ $user["local_identity"] = true;
+ } else {
+ $user["identity"] = simpleid_url('user/' . rawurlencode($uid));
+ $user["local_identity"] = false;
+ }
+
+ return $user;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Loads user data for a specified OpenID Identity URI.
+ *
+ * @param string $identity the Identity URI of the user to load
+ * @return mixed data for the specified user, or NULL if the user name does not
+ * exist
+ * @see user_load()
+ */
+function user_load_from_identity($identity) {
+ $uid = store_get_uid($identity);
+ if ($uid !== NULL) return user_load($uid);
+
+ return NULL;
+}
+
+/**
+ * Stores user data for a specified user name.
+ *
+ * @param array $user the user to save
+ */
+function user_save($user) {
+ $uid = $user['uid'];
+ store_user_save($uid, $user, array('uid', 'identity', 'pass'));
+}
+
+/**
+ * Attempts to log in a user, using the user name and password specified in the
+ * HTTP request.
+ */
+function user_login() {
+ global $user, $GETPOST;
+
+ // If the user is already logged in, return
+ if (isset($user['uid'])) openid_indirect_response(simpleid_url(), '');
+
+ // Require HTTPS or return an error
+ check_https('error', true);
+
+ $destination = (isset($GETPOST['destination'])) ? $GETPOST['destination'] : '';
+ $state = (isset($GETPOST['s'])) ? $GETPOST['s'] : '';
+ $fixed_uid = (isset($_POST['fixed_uid'])) ? $_POST['name'] : NULL;
+ $mode = $_POST['mode'];
+
+ $query = ($state) ? 's=' . rawurlencode($state) : '';
+
+ if (isset($_POST['op']) && $_POST['op'] == t('Cancel')) {
+ global $version;
+
+ $request = unpickle($state);
+ $version = openid_get_version($request);
+
+ if (isset($request['openid.return_to'])) {
+ $return_to = $request['openid.return_to'];
+ $response = simpleid_checkid_error(FALSE);
+ simpleid_assertion_response($response, $return_to);
+ } else {
+ indirect_fatal_error(t('Login cancelled without a proper OpenID request.'));
+ }
+ return;
+ }
+
+ if (!isset($_POST['mode']) || !in_array($_POST['mode'], array('credentials', 'otp'))) {
+ set_message(t('SimpleID detected a potential security attack on your log in. Please log in again.'));
+ user_login_form($destination, $state, $fixed_uid);
+ return;
+ }
+
+ if (!isset($_POST['nonce'])) {
+ if (isset($_POST['destination'])) {
+ // User came from a log in form.
+ set_message(t('You seem to be attempting to log in from another web page. You must use this page to log in.'));
+ }
+ user_login_form($destination, $state, $fixed_uid, $mode);
+ return;
+ }
+
+ $time = strtotime(substr($_POST['nonce'], 0, 20));
+ // Some old versions of PHP does not recognise the T in the ISO 8601 date. We may need to convert the T to a space
+ if (($time == -1) || ($time === FALSE)) $time = strtotime(strtr(substr($_POST['nonce'], 0, 20), 'T', ' '));
+ $nonce = cache_get('user-nonce', $_POST['nonce']);
+
+ if (!$nonce) {
+ log_warn('Login attempt: Nonce ' . $_POST['nonce'] . ' not issued or is being reused.');
+ set_message(t('SimpleID detected a potential security attack on your log in. Please log in again.'));
+ user_login_form($destination, $state, $fixed_uid, $mode);
+ return;
+ } elseif ($time < time() - SIMPLEID_LOGIN_NONCE_EXPIRES_IN) {
+ log_notice('Login attempt: Nonce ' . $_POST['nonce'] . ' expired.');
+ set_message(t('The log in page has expired. Please log in again.'));
+ user_login_form($destination, $state, $fixed_uid, $mode);
+ return;
+ } elseif ($nonce['mode'] != $mode) {
+ log_warn('Login attempt: Mode saved with nonce ' . $_POST['nonce'] . ' (' . $nonce['mode'] . ') does not match ' . $mode);
+ set_message(t('SimpleID detected a potential security attack on your log in. Please log in again.'));
+ user_login_form($destination, $state, $fixed_uid, $mode);
+ } else {
+ cache_delete('user-nonce', $_POST['nonce']);
+ }
+
+ switch ($mode) {
+ case 'credentials':
+ if (!isset($_POST['name'])) $_POST['name'] = '';
+ if (!isset($_POST['pass'])) $_POST['pass'] = '';
+
+ if (($_POST['name'] == '') || ($_POST['pass'] == '')) {
+ if (isset($_POST['destination'])) {
+ // User came from a log in form.
+ set_message(t('You need to supply the user name and the password in order to log in.'));
+ }
+ if (isset($_POST['nonce'])) cache_delete('user-nonce', $_POST['nonce']);
+ user_login_form($destination, $state, $fixed_uid);
+ return;
+ }
+
+ if (user_verify_credentials($_POST['name'], $_POST) === false) {
+ set_message(t('The user name or password is not correct.'));
+ user_login_form($destination, $state, $fixed_uid);
+ return;
+ }
+
+ $test_user = user_load($_POST['name']);
+ if (isset($test_user['otp']) && ($test_user['otp']['type'] != 'recovery')) {
+ log_info('One time password required');
+ user_login_form($destination, $state, $test_user['uid'], 'otp');
+ return;
+ }
+ break;
+ case 'otp':
+ if (!isset($_POST['otp']) || ($_POST['otp'] == '')) {
+ set_message(t('You need to enter the verification code in order to log in.'));
+ if (isset($_POST['nonce'])) cache_delete('user-nonce', $_POST['nonce']);
+ user_login_form($destination, $state, $nonce['uid'], 'otp');
+ return;
+ }
+
+ $test_user = user_load($nonce['uid']);
+
+ if (user_verify_otp($test_user['otp'], $_POST['otp']) === false) {
+ set_message(t('The verification code is not correct.'));
+ user_login_form($destination, $state, $nonce['uid'], 'otp');
+ return;
+ }
+ user_save($test_user); // Save the drift
+
+ break;
+ }
+
+ _user_login($test_user, true);
+
+ openid_indirect_response(simpleid_url($destination, $query), '');
+}
+
+/**
+ * Verifies a set of credentials for a specified user.
+ *
+ * A set of credentials comprises:
+ *
+ * - A user name
+ * - Some kind of verifying information, such as a plaintext password, a hashed
+ * password (e.g. digest) or some other kind of identifying information.
+ *
+ * The user name is passed to this function using the $uid parameter. The user
+ * name may or may not exist. If the user name does not exist, this function
+ * <strong>must</strong> return false.
+ *
+ * The credentials are supplied as an array using the $credentials parameter.
+ * Typically this array will be a subset of the $_POST superglobal passed to the
+ * {@link user_login()} function. Thus it will generally contain the keys 'pass' and
+ * 'digest'.
+ *
+ * This function calls the {@link hook_user_verify_credentials()} hook to
+ * check whether the credentials supplied matches the credentials
+ * for the specified user in the store.
+ *
+ * @param string $uid the name of the user to verify
+ * @param array $credentials the credentials supplied by the browser
+ * @return bool whether the credentials supplied matches those for the specified
+ * user
+ */
+function user_verify_credentials($uid, $credentials) {
+ global $simpleid_extensions;
+
+ $extensions = $simpleid_extensions;
+
+ if (!in_array('user_passauth', $extensions)) $extensions[] = 'user_passauth';
+
+ foreach ($extensions as $extension) {
+ $result = extension_invoke($extension, 'user_verify_credentials', $uid, $credentials);
+ if ($result === true) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Verifies a one time password (OTP) specified by the user.
+ *
+ * This function compares an OTP supplied by a user with the OTP
+ * calculated based on the current time and the parameters of the
+ * algorithm. The parameters, such as the secret key, are supplied
+ * using in $params. These parameters are typically stored for each
+ * user in the user store.
+ *
+ * To allow for clocks going out of sync, the current time will be
+ * by a number (in time steps) specified in $params['drift']. If
+ * the OTP supplied by the user is accepted, $params['drift'] will
+ * be also be updated with the latest difference.
+ *
+ * To allow for network delay, the function will accepts OTPs which
+ * is a number of time steps away from the OTP calculated from the
+ * adjusted time. The maximum number of time steps is specified in
+ * the $max_drift parameter.
+ *
+ * @param array &$params the OTP parameters stored
+ * @param string $code the OTP supplied by the user
+ * @param int $max_drift the maximum drift allowed for network delay, in
+ * time steps
+ * @return bool whether the OTP supplied matches the OTP generated based on
+ * the specified parameters, within the maximum drift
+ */
+function user_verify_otp(&$params, $code, $max_drift = 1) {
+ switch ($params['type']) {
+ case 'totp':
+ $time = time();
+
+ $test_code = user_totp($params['secret'], $time, $params['period'], $params['drift'], $params['algorithm'], $params['digits']);
+
+ if ($test_code == intval($code)) return true;
+
+ for ($i = -$max_drift; $i <= $max_drift; $i++) {
+ $test_code = user_totp($params['secret'], $time, $params['period'], $params['drift'] + $i, $params['algorithm'], $params['digits']);
+ if ($test_code == intval($code)) {
+ $params['drift'] = $i;
+ return true;
+ }
+ }
+ return false;
+ break;
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Sets the user specified by the parameter as the active user.
+ *
+ * @param array $login_user the user to log in
+ * @param bool $auth_active whether the user has been actively authenticated
+ * in this session
+ *
+ */
+function _user_login($login_user, $auth_active = false) {
+ global $user;
+
+ if ($auth_active) {
+ // Set the current authentication time
+ $login_user['auth_time'] = time();
+ user_save($login_user);
+
+ // Set user has been actively authenticated this and the next request only
+ $login_user['auth_active'] = true;
+ $_SESSION['user_auth_active'] = true;
+ log_info('Login successful: ' . $login_user['uid'] . '['. gmstrftime('%Y-%m-%dT%H:%M:%SZ', $login_user['auth_time']) . ']');
+
+ }
+
+ $user = $login_user;
+ $_SESSION['user'] = $login_user['uid'];
+ cache_set('user', $login_user['uid'], session_id());
+
+
+ if ($auth_active) {
+ if (isset($_POST['autologin']) && ($_POST['autologin'] == 1)) user_cookieauth_create_cookie();
+ }
+}
+
+/**
+ * Attempts to log out a user and returns to the login form.
+ *
+ * @param string $destination the destination value to be included in the
+ * login form
+ */
+function user_logout($destination = NULL) {
+ global $user, $GETPOST;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ $state = (isset($GETPOST['s'])) ? $GETPOST['s'] : '';
+ if ($destination == NULL) {
+ if (isset($GETPOST['destination'])) {
+ $destination = $GETPOST['destination'];
+ } else {
+ $destination = '';
+ }
+ }
+
+ _user_logout();
+
+ set_message(t('You have been logged out.'));
+
+ user_login_form($destination, $state);
+}
+
+/**
+ * Logs out the user by deleting the relevant session information.
+ */
+function _user_logout() {
+ global $user;
+
+ $uid = $user['uid'];
+
+ user_cookieauth_invalidate();
+ session_destroy();
+
+ cache_delete('user', $uid);
+ unset($_SESSION['user']);
+ $user = NULL;
+
+ log_info('Logout successful: ' . $uid);
+}
+
+/**
+ * Displays a user login or a login verification form.
+ *
+ * @param string $destination the SimpleID location to which the user is directed
+ * if login is successful
+ * @param string $state the current SimpleID state, if required by the location
+ * @param string $fixed_uid the user name to be included in the login form; if NULL, the user
+ * is asked to supply the user name. If $mode is otp this cannot be null
+ * @param string $mode either credentials (login form) or otp (login verification
+ * form)
+ */
+function user_login_form($destination = '', $state = NULL, $fixed_uid = NULL, $mode = 'credentials') {
+ global $xtpl;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ if ($state) {
+ $xtpl->assign('state', htmlspecialchars($state, ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('cancel_button', t('Cancel'));
+ $xtpl->parse('main.login.state');
+ }
+
+ cache_expire(array('user-nonce' => SIMPLEID_LOGIN_NONCE_EXPIRES_IN));
+ $nonce = openid_nonce();
+ cache_set('user-nonce', $nonce, array('mode' => $mode, 'uid' => $fixed_uid));
+
+ $base_path = get_base_path();
+ $xtpl->assign('javascript', '<script src="' . $base_path . 'html/user-login.js" type="text/javascript"></script>');
+
+ header('X-Frame-Options: DENY');
+
+ switch ($mode) {
+ case 'credentials':
+ $security_class = (SIMPLEID_ALLOW_AUTOCOMPLETE) ? 'allow-autocomplete ' : '';
+ if (is_https()) {
+ $security_class .= 'secure';
+ $xtpl->assign('security_message', t('Secure login using <strong>HTTPS</strong>.'));
+ } elseif (SIMPLEID_ALLOW_PLAINTEXT) {
+ $security_class .= 'unsecure';
+ $xtpl->assign('security_message', t('<strong>WARNING:</strong> Your password will be sent to SimpleID as plain text.'));
+ }
+ $xtpl->assign('security_class', $security_class);
+ $xtpl->parse('main.login.login_security');
+
+ extension_invoke_all('user_login_form', $destination, $state);
+
+ $xtpl->assign('name_label', t('User name:'));
+ $xtpl->assign('pass_label', t('Password:'));
+ $xtpl->assign('autologin_label', t('Remember me on this computer for two weeks.'));
+
+ if ($fixed_uid == NULL) {
+ $xtpl->parse('main.login.credentials.input_uid');
+ } else {
+ $xtpl->assign('uid', htmlspecialchars($fixed_uid, ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('main.login.credentials.fixed_uid');
+ }
+
+ $xtpl->parse('main.login.credentials');
+ $xtpl->assign('submit_button', t('Log in'));
+ $xtpl->assign('title', t('Log In'));
+ break;
+ case 'otp':
+ // Note this is called from user_login(), so $_POST is always filled
+ $xtpl->assign('otp_instructions_label', t('To verify your identity, enter the verification code.'));
+ $xtpl->assign('otp_recovery_label', t('If you have lost your verification code, you can <a href="!url">recover your account</a>.',
+ array('!url' => 'http://simpleid.org/docs/1/common-problems/#otp')
+ ));
+
+ $xtpl->assign('otp_label', t('Verification code:'));
+ $xtpl->assign('autologin', (isset($_POST['autologin']) && ($_POST['autologin'] == 1)) ? '1' : '0');
+ $xtpl->parse('main.login.otp');
+
+ $xtpl->assign('submit_button', t('Verify'));
+ $xtpl->assign('title', t('Enter Verification Code'));
+ default:
+ }
+
+
+ $xtpl->assign('mode', $mode);
+ $xtpl->assign('page_class', 'dialog-page');
+ $xtpl->assign('destination', htmlspecialchars($destination, ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('nonce', htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8'));
+
+ $xtpl->parse('main.login');
+ $xtpl->parse('main.framekiller');
+ $xtpl->parse('main');
+ $xtpl->out('main');
+}
+
+/**
+ * Displays the page used to set up login verification using one-time
+ * passwords.
+ */
+function user_otp_page() {
+ global $xtpl, $user;
+
+ // Require HTTPS, redirect if necessary
+ check_https('redirect', true);
+
+ if ($user == NULL) {
+ user_login_form('my/profile');
+ return;
+ }
+
+ if ($_POST['op'] == t('Disable')) {
+ if (!isset($_POST['tk']) || !validate_form_token($_POST['tk'], 'dashboard_otp')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ page_dashboard();
+ return;
+ }
+
+ if (isset($user['otp'])) {
+ unset($user['otp']);
+ user_save($user);
+ }
+ set_message('Login verification has been disabled.');
+ page_dashboard();
+ return;
+ } elseif ($_POST['op'] == t('Verify')) {
+ $params = $_SESSION['otp_setup'];
+
+ if (!isset($_POST['tk']) || !validate_form_token($_POST['tk'], 'otp')) {
+ set_message(t('SimpleID detected a potential security attack. Please try again.'));
+ page_dashboard();
+ return;
+ } elseif (!isset($_POST['otp']) || ($_POST['otp'] == '')) {
+ set_message(t('You need to enter the verification code to complete enabling login verification.'));
+ } elseif (user_verify_otp($params, $_POST['otp'], 10) === false) {
+ set_message(t('The verification code is not correct.'));
+ } else {
+ unset($_SESSION['otp_setup']);
+ $user['otp'] = $params;
+ user_save($user);
+
+ set_message('Login verification has been enabled.');
+ page_dashboard();
+ return;
+ }
+ } else {
+ $params = array(
+ 'type' => 'totp',
+ 'secret' => random_bytes(10),
+ 'algorithm' => 'sha1',
+ 'digits' => 6,
+ 'period' => 30,
+ 'drift' => 0,
+ );
+ $_SESSION['otp_setup'] = $params;
+ }
+
+ $code = strtr(bignum_val(bignum_new($params['secret'], 256), 32), '0123456789abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567');
+ for ($i = 0; $i < strlen($code); $i += 4) {
+ $xtpl->assign('secret' . ($i + 1), substr($code, $i, 4));
+ }
+
+ $url = 'otpauth://totp/SimpleID?secret=' . $code . '&digits=' . $params['digits'] . '&period=' . $params['period'];
+ $xtpl->assign('qr', addslashes($url));
+
+ $xtpl->assign('about_otp', t('Login verification adds an extra layer of protection to your account. When enabled, you will need to enter an additional security code whenever you log into SimpleID.'));
+ $xtpl->assign('otp_warning', t('<strong>WARNING:</strong> If you enable login verification and lose your authenticator app, you will need to <a href="!url">edit your identity file manually</a> before you can log in again.',
+ array('!url' => 'http://simpleid.org/docs/1/common-problems/#otp')
+ ));
+
+ $xtpl->assign('setup_otp', t('To set up login verification, following these steps.'));
+ $xtpl->assign('download_app', t('Download an authenticator app that supports TOTP for your smartphone, such as Google Authenticator.'));
+ $xtpl->assign('add_account', t('Add your SimpleID account to authenticator app using this key. If you are viewing this page on your smartphone you can use <a href="!url">this link</a> or scan the QR code to add your account.',
+ array('!url' => $url)
+ ));
+ $xtpl->assign('verify_code', t('To check that your account has been added properly, enter the verification code from your phone into the box below, and click Verify.'));
+
+ $xtpl->assign('token', get_form_token('otp'));
+ $xtpl->assign('otp_label', t('Verification code:'));
+ $xtpl->assign('submit_button', t('Verify'));
+
+ $xtpl->assign('page_class', 'dialog-page');
+ $xtpl->assign('title', t('Login Verification'));
+
+ $xtpl->parse('main.otp');
+ $xtpl->parse('main.framekiller');
+
+ $xtpl->parse('main');
+ $xtpl->out('main');
+
+}
+
+
+/**
+ * Returns the user's public page.
+ *
+ * @param string $uid the user ID
+ */
+function user_public_page($uid = NULL) {
+ global $xtpl, $user;
+
+ $xtpl->assign('title', t('User Page'));
+ if ($uid == NULL) {
+ header_response_code('400 Bad Request');
+ set_message(t('No user specified.'));
+ } else {
+ $user = user_load($uid);
+
+ if ($user == NULL) {
+ header_response_code('404 Not Found');
+ set_message(t('User %uid not found.', array('%uid' => $uid)));
+ } else {
+ header('Vary: Accept');
+
+ $content_type = negotiate_content_type(array('text/html', 'application/xml', 'application/xhtml+xml', 'application/xrds+xml'));
+
+ if ($content_type == 'application/xrds+xml') {
+ user_xrds($uid);
+ return;
+ } else {
+ header('X-XRDS-Location: ' . simpleid_url('xrds/' . rawurlencode($uid)));
+
+ set_message(t('This is the user %uid\'s SimpleID page. It contains hidden information for the use by OpenID consumers.', array('%uid' => $uid)));
+
+ $xtpl->assign('title', htmlspecialchars($uid, ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('provider', htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('xrds', htmlspecialchars(simpleid_url('xrds/' . rawurlencode($uid)), ENT_QUOTES, 'UTF-8'));
+ if ($user["local_identity"]) {
+ $xtpl->assign('local_id', htmlspecialchars($user["identity"], ENT_QUOTES, 'UTF-8'));
+ }
+ }
+ }
+ }
+
+ $xtpl->parse('main.provider');
+ if ($user["local_identity"]) $xtpl->parse('main.local_id');
+ $xtpl->parse('main');
+ $xtpl->out('main');
+}
+
+/**
+ * Returns the public page for a private personal ID.
+ *
+ * @param string $ppid the PPID
+ */
+function user_ppid_page($ppid = NULL) {
+ global $xtpl;
+
+ header('Vary: Accept');
+
+ $content_type = negotiate_content_type(array('text/html', 'application/xml', 'application/xhtml+xml', 'application/xrds+xml'));
+
+ if (($content_type == 'application/xrds+xml') || ($_GET['format'] == 'xrds')) {
+ header('Content-Type: application/xrds+xml');
+ header('Content-Disposition: inline; filename=yadis.xml');
+
+ $xtpl->assign('simpleid_base_url', htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('xrds.user_xrds');
+ $xtpl->parse('xrds');
+ $xtpl->out('xrds');
+ return;
+ } else {
+ header('X-XRDS-Location: ' . simpleid_url('ppid/' . rawurlencode($ppid), 'format=xrds'));
+
+ $xtpl->assign('title', t('Private Personal Identifier'));
+
+ set_message(t('This is a private personal identifier.'));
+
+ $xtpl->parse('main');
+ $xtpl->out('main');
+ }
+}
+
+/**
+ * Returns the user's public XRDS page.
+ *
+ * @param string $uid the user ID
+ */
+function user_xrds($uid) {
+ global $xtpl;
+
+ $user = user_load($uid);
+
+ if ($user != NULL) {
+ header('Content-Type: application/xrds+xml');
+ header('Content-Disposition: inline; filename=yadis.xml');
+
+ if (($user != NULL) && ($user["local_identity"])) {
+ $xtpl->assign('local_id', htmlspecialchars($user["identity"], ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('xrds.user_xrds.local_id');
+ $xtpl->parse('xrds.user_xrds.local_id2');
+ }
+
+ $xtpl->assign('simpleid_base_url', htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8'));
+ $xtpl->parse('xrds.user_xrds');
+ $xtpl->parse('xrds');
+ $xtpl->out('xrds');
+ } else {
+ if (substr(PHP_SAPI, 0,3) === 'cgi') {
+ header('Status: 404 Not Found');
+ } else {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
+ }
+
+ set_message('User <strong>' . htmlspecialchars($uid, ENT_QUOTES, 'UTF-8') . '</strong> not found.');
+ $xtpl->parse('main');
+ $xtpl->out('main');
+ }
+}
+
+/**
+ * Returns a block containing OpenID Connect user information.
+ *
+ * @return array the OpenID Connect user information block
+ */
+function _user_page_profile() {
+ global $user;
+
+ $html = '<p>' . t('SimpleID may, with your consent, send the following information to sites which supports OpenID Connect.') . '</p>';
+ $html .= '<p>' . t('To change these, <a href="!url">edit your identity file</a>.', array('!url' => 'http://simpleid.org/docs/1/identity-files/')) . '</p>';
+
+ $html .= "<table><tr><th>" . t('Member') . "</th><th>" . t('Value') . "</th></tr>";
+
+ if (isset($user['user_info'])) {
+ foreach ($user['user_info'] as $member => $value) {
+ if (is_array($value)) {
+ foreach ($value as $submember => $subvalue) {
+ $html .= "<tr><td>" . htmlspecialchars($member, ENT_QUOTES, 'UTF-8') . " (" .htmlspecialchars($submember, ENT_QUOTES, 'UTF-8') . ")</td><td>" . htmlspecialchars($subvalue, ENT_QUOTES, 'UTF-8') . "</td></tr>";
+ }
+ } else {
+ $html .= "<tr><td>" . htmlspecialchars($member, ENT_QUOTES, 'UTF-8') . "</td><td>" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . "</td></tr>";
+ }
+ }
+ }
+
+ $html .= "</table>";
+
+ return array(array(
+ 'id' => 'userinfo',
+ 'title' => t('OpenID Connect'),
+ 'content' => $html
+ ));
+}
+
+/**
+ * Set up the user section in the header, showing the currently logged in user.
+ *
+ * @param string $state the SimpleID state to retain once the user has logged out,
+ * if required.
+ */
+function user_header($state = NULL) {
+ global $user;
+ global $xtpl;
+
+ if ($user != NULL) {
+ $xtpl->assign('uid', htmlspecialchars($user['uid'], ENT_QUOTES, 'UTF-8'));
+ $xtpl->assign('identity', htmlspecialchars($user['identity'], ENT_QUOTES, 'UTF-8'));
+ if ($state != NULL) {
+ $xtpl->assign('url', htmlspecialchars(simpleid_url('logout', 'destination=continue&s=' . rawurlencode($state), true)));
+ $xtpl->assign('logout', t('Log out and log in as a different user'));
+ } else {
+ $xtpl->assign('url', htmlspecialchars(simpleid_url('logout', '', true)));
+ $xtpl->assign('logout', t('Log out'));
+ }
+ $xtpl->parse('main.user.logout');
+ $xtpl->parse('main.user');
+ }
+}
+
+/**
+ * Verifies a set of credentials using the default user name-password authentication
+ * method.
+ *
+ * @param string $uid the name of the user to verify
+ * @param array $credentials the credentials supplied by the browser
+ * @return bool whether the credentials supplied matches those for the specified
+ * user
+ */
+function user_passauth_user_verify_credentials($uid, $credentials) {
+ $allowed_algorithms = array('md5', 'sha1');
+ if (function_exists('hash_algos')) $allowed_algorithms = array_merge($allowed_algorithms, hash_algos());
+ if (function_exists('hash_pbkdf2')) $allowed_algorithms[] = 'pbkdf2';
+
+ $test_user = user_load($uid);
+
+ if ($test_user == NULL) return false;
+
+ $hash_function_salt = explode(':', $test_user['pass'], 3);
+
+ $hash = $hash_function_salt[0];
+ $function = (isset($hash_function_salt[1])) ? $hash_function_salt[1] : 'md5';
+ if (!in_array($function, $allowed_algorithms)) $function = 'md5';
+ $salt_suffix = (isset($hash_function_salt[2])) ? ':' . $hash_function_salt[2] : '';
+
+ switch ($function) {
+ case 'pbkdf2':
+ list ($algo, $iterations, $salt) = explode(':', $hash_function_salt[2]);
+ $length = (function_exists('hash')) ? strlen(hash($algo, '')) : 0;
+ $test_hash = hash_pbkdf2($algo, $credentials['pass'], $salt, $iterations, $length);
+ break;
+ case 'md5':
+ case 'sha1':
+ $test_hash = call_user_func($function, $credentials['pass'] . $salt_suffix);
+ break;
+ default:
+ $test_hash = hash($function, $credentials['pass'] . $salt_suffix);
+ }
+
+ return secure_compare($test_hash, $hash);
+}
+
+/**
+ * Creates a auto login cookie. The login cookie will be based on the
+ * current log in user.
+ *
+ * @param string $id the ID of the series of auto login cookies, Cookies
+ * belonging to the same user and computer have the same ID. If none is specified,
+ * one will be generated
+ * @param int $expires the time at which the cookie will expire. If none is specified
+ * the time specified in {@link SIMPLEID_USER_AUTOLOGIN_EXPIRES_IN} will be
+ * used
+ *
+ */
+function user_cookieauth_create_cookie($id = NULL, $expires = NULL) {
+ global $user;
+
+ if ($expires == NULL) {
+ log_debug('Automatic login token created for ' . $user['uid']);
+ } else {
+ log_debug('Automatic login token renewed for ' . $user['uid']);
+ }
+
+ if ($id == NULL) $id = random_id();
+ if ($expires == NULL) $expires = time() + SIMPLEID_USER_AUTOLOGIN_EXPIRES_IN;
+ $token = random_secret();
+ $uid_hash = get_form_token($user['uid'], FALSE);
+
+ $data = array(
+ 'uid' => $user['uid'],
+ 'token' => $token,
+ 'expires' => $expires,
+ 'uaid' => get_user_agent_id(),
+ 'ip' => $_SERVER['REMOTE_ADDR']
+ );
+
+ cache_set('autologin-'. $uid_hash, $id, $data);
+
+ // Note the last parameter (httponly) requires PHP 5.2
+ setcookie(simpleid_cookie_name('auth'), 'cookieauth:' . $uid_hash . ':' . $id . ':' . $token, $expires, get_base_path(), '', false, true);
+}
+
+/**
+ * Verifies a auto login cookie. If valid, log in the user automatically.
+ */
+function user_cookieauth_user_auto_login() {
+ if (!isset($_COOKIE[simpleid_cookie_name('auth')])) return NULL;
+
+ $cookie = $_COOKIE[simpleid_cookie_name('auth')];
+
+ list($authtype, $uid_hash, $id, $token) = explode(':', $cookie);
+ if ($authtype != 'cookieauth') return NULL;
+
+ log_debug('Automatic login token detected: ' . implode(':', ['cookieauth', $uid_hash, $id]));
+
+ cache_expire(array('autologin-' . $uid_hash => SIMPLEID_USER_AUTOLOGIN_EXPIRES_IN));
+ $data = cache_get('autologin-' . $uid_hash, $id);
+
+ if (!$data) { // Cookie doesn't exist
+ log_notice('Automatic login: Token does not exist on server');
+ return NULL;
+ }
+
+ if ($data['expires'] < time()) { // Cookie expired
+ log_notice('Automatic login: Token on server expired');
+ return NULL;
+ }
+
+ if ($data['token'] != $token) {
+ log_warn('Automatic login: Token on server does not match');
+ // Token not the same - panic
+ cache_expire(array('autologin-' . $uid_hash => 0));
+ user_cookieauth_invalidate();
+ return NULL;
+ }
+
+ if ($data['uaid'] != get_user_agent_id()) {
+ log_warn('Automatic login: User agent ID does not match');
+ // Token not the same - panic
+ cache_expire(array('autologin-' . $uid_hash => 0));
+ user_cookieauth_invalidate();
+ return NULL;
+ }
+
+ // Load the user, tag it as an auto log in
+ $test_user = user_load($data['uid']);
+
+ if ($test_user != NULL) {
+ log_debug('Automatic login token accepted for ' . $data['uid']);
+
+ $test_user['autologin'] = TRUE;
+
+ // Renew the token
+ user_cookieauth_create_cookie($id, $data['expires']);
+
+ return $test_user;
+ } else {
+ log_warn('Automatic login token accepted for ' . $data['uid'] . ', but no such user exists');
+ return NULL;
+ }
+}
+
+/**
+ * Removes the auto login cookie from the user agent and the SimpleID
+ * cache.
+ */
+function user_cookieauth_invalidate() {
+ if (isset($_COOKIE[simpleid_cookie_name('auth')])) {
+ $cookie = $_COOKIE[simpleid_cookie_name('auth')];
+
+ list($uid_hash, $id, $token) = explode(':', $cookie);
+
+ cache_delete('autologin-' . $uid_hash, $id);
+
+ setcookie(simpleid_cookie_name('auth'), "", time() - 3600);
+ }
+}
+
+/**
+ * Calculates a Time-Based One-Time Password (TOTP) based on RFC 6238.
+ *
+ * This function returns an integer calculated from the TOTP algorithm.
+ * The returned integer may need to be zero-padded to return a string
+ * with the required number of digits
+ *
+ * @param string $secret the shared secret as a binary string
+ * @param int $time the time to use in the HOTP algorithm. If NULL, the
+ * current time is used
+ * @param int $period the time step in seconds
+ * @param int $drift the number of time steps to be added to the time to
+ * adjust for transmission delay
+ * @param string $algorithm the hashing algorithm as supported by
+ * the hash_hmac() function
+ * @param int $digits the number of digits in the one-time password
+ * @return int the one-time password
+ * @link http://tools.ietf.org/html/rfc6238
+ */
+function user_totp($secret, $time = NULL, $period = 30, $drift = 0, $algorithm = 'sha1', $digits = 6) {
+ if ($time == NULL) $time = time();
+ $counter = floor($time / $period) + $drift;
+ $data = pack('NN', 0, $counter);
+ return user_hotp($secret, $data, $algorithm, $digits);
+}
+
+/**
+ * Calculates a HMAC-Based One-Time Password (HOTP) based on RFC 4226.
+ *
+ * This function returns an integer calculated from the HOTP algorithm.
+ * The returned integer may need to be zero-padded to return a string
+ * with the required number of digits
+ *
+ * @param string $secret the shared secret as a binary string
+ * @param string $data the counter value as a 64 bit in
+ * big endian encoding
+ * @param string $algorithm the hashing algorithm as supported by
+ * the hash_hmac() function
+ * @param int $digits the number of digits in the one-time password
+ * @return int the one-time password
+ * @link http://tools.ietf.org/html/rfc4226
+ */
+function user_hotp($secret, $data, $algorithm = 'sha1', $digits = 6) {
+ // unpack produces a 1-based array, we use array_merge to convert it to 0-based
+ $hmac = array_merge(unpack('C*', hash_hmac(strtolower($algorithm), $data, $secret, true)));
+ $offset = $hmac[19] & 0xf;
+ $code = ($hmac[$offset + 0] & 0x7F) << 24 |
+ ($hmac[$offset + 1] & 0xFF) << 16 |
+ ($hmac[$offset + 2] & 0xFF) << 8 |
+ ($hmac[$offset + 3] & 0xFF);
+ return $code % pow(10, $digits);
+}
+
+
+if (!function_exists('hash_pbkdf2') && function_exists('hash_hmac')) {
+ function hash_pbkdf2($algo, $password, $salt, $iterations, $length = 0, $raw_output = false) {
+ $result = '';
+ $hLen = strlen(hash($algo, '', true));
+ if ($length == 0) {
+ $length = $hLen;
+ if (!$raw_output) $length *= 2;
+ }
+ $l = ceil($length / $hLen);
+
+ for ($i = 1; $i <= $l; $i++) {
+ $U = hash_hmac($algo, $salt . pack('N', $i), $password, true);
+ $T = $U;
+ for ($j = 1; $j < $iterations; $j++) {
+ $T ^= ($U = hash_hmac($algo, $U, $password, true));
+ }
+ $result .= $T;
+ }
+
+ return substr(($raw_output) ? $result : bin2hex($result), 0, $length);
+ }
+}
+
+
+?>
diff --git a/simpleid/www/version.inc.php b/simpleid/www/version.inc.php
new file mode 100644
index 0000000..fb318c2
--- /dev/null
+++ b/simpleid/www/version.inc.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-8
+ *
+ * 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$
+ */
+
+/**
+ * Defines the current version of SimpleID.
+ *
+ * @package simpleid
+ */
+
+/**
+ * The current version of SimpleID
+ */
+define('SIMPLEID_VERSION', '1.0.5');
+?>