From 593e2792f1e2e68a47a2e74df3dd9699c4005fa9 Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Sat, 12 Jun 2010 20:08:49 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=B0=20trunk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +YUCHENG HU+ git-svn-id: https://svn.code.sf.net/p/hawebs/svn@218 a2543c7e-f6e9-4f8a-8bff-1ffc34733512 --- .../trunk/libraries/openid/Auth/OpenID.php | 555 ++++ .../trunk/libraries/openid/Auth/OpenID/AX.php | 1026 +++++++ .../openid/Auth/OpenID/Association.php | 616 ++++ .../libraries/openid/Auth/OpenID/BigMath.php | 474 +++ .../libraries/openid/Auth/OpenID/Consumer.php | 2232 ++++++++++++++ .../openid/Auth/OpenID/CryptUtil.php | 112 + .../openid/Auth/OpenID/DatabaseConnection.php | 134 + .../openid/Auth/OpenID/DiffieHellman.php | 116 + .../libraries/openid/Auth/OpenID/Discover.php | 551 ++++ .../openid/Auth/OpenID/DumbStore.php | 103 + .../openid/Auth/OpenID/Extension.php | 65 + .../openid/Auth/OpenID/FileStore.php | 621 ++++ .../libraries/openid/Auth/OpenID/HMAC.php | 102 + .../openid/Auth/OpenID/Interface.php | 200 ++ .../libraries/openid/Auth/OpenID/KVForm.php | 115 + .../openid/Auth/OpenID/MemcachedStore.php | 211 ++ .../libraries/openid/Auth/OpenID/Message.php | 918 ++++++ .../openid/Auth/OpenID/MySQLStore.php | 81 + .../libraries/openid/Auth/OpenID/Nonce.php | 112 + .../libraries/openid/Auth/OpenID/PAPE.php | 304 ++ .../libraries/openid/Auth/OpenID/Parse.php | 355 +++ .../openid/Auth/OpenID/PostgreSQLStore.php | 116 + .../libraries/openid/Auth/OpenID/SQLStore.php | 572 ++++ .../openid/Auth/OpenID/SQLiteStore.php | 74 + .../libraries/openid/Auth/OpenID/SReg.php | 524 ++++ .../libraries/openid/Auth/OpenID/Server.php | 1763 +++++++++++ .../openid/Auth/OpenID/ServerRequest.php | 40 + .../openid/Auth/OpenID/TrustRoot.php | 465 +++ .../libraries/openid/Auth/OpenID/URINorm.php | 252 ++ .../libraries/openid/Auth/OpenID/index.html | 4 + .../openid/Auth/Yadis/HTTPFetcher.php | 150 + .../libraries/openid/Auth/Yadis/Manager.php | 532 ++++ .../libraries/openid/Auth/Yadis/Misc.php | 62 + .../openid/Auth/Yadis/ParanoidHTTPFetcher.php | 231 ++ .../libraries/openid/Auth/Yadis/ParseHTML.php | 262 ++ .../openid/Auth/Yadis/PlainHTTPFetcher.php | 254 ++ .../trunk/libraries/openid/Auth/Yadis/XML.php | 377 +++ .../libraries/openid/Auth/Yadis/XRDS.php | 481 +++ .../trunk/libraries/openid/Auth/Yadis/XRI.php | 237 ++ .../libraries/openid/Auth/Yadis/XRIRes.php | 75 + .../libraries/openid/Auth/Yadis/Yadis.php | 385 +++ .../libraries/openid/Auth/Yadis/index.html | 4 + .../trunk/libraries/openid/Auth/index.html | 4 + oss/joomla/trunk/libraries/openid/CHANGELOG | 17 + .../trunk/libraries/openid/CHANGES-2.1.0 | 51 + oss/joomla/trunk/libraries/openid/COPYING | 202 ++ oss/joomla/trunk/libraries/openid/NEWS | 85 + oss/joomla/trunk/libraries/openid/README | 139 + .../trunk/libraries/openid/consumer.php | 32 + oss/joomla/trunk/libraries/openid/index.html | 4 + .../trunk/libraries/pattemplate/index.html | 1 + .../trunk/libraries/pattemplate/patError.php | 297 ++ .../libraries/pattemplate/patErrorManager.php | 710 +++++ .../libraries/pattemplate/patTemplate.php | 2670 +++++++++++++++++ .../pattemplate/patTemplate/Compiler.php | 485 +++ .../pattemplate/patTemplate/Dump.php | 160 + .../pattemplate/patTemplate/Dump/Html.php | 658 ++++ .../pattemplate/patTemplate/Dump/XUL.php | 229 ++ .../pattemplate/patTemplate/Dump/index.html | 4 + .../pattemplate/patTemplate/Function.php | 77 + .../patTemplate/Function/Alias.php | 57 + .../patTemplate/Function/Attribute.php | 57 + .../pattemplate/patTemplate/Function/Call.php | 119 + .../patTemplate/Function/Globalvar.php | 88 + .../patTemplate/Function/Highlight.php | 64 + .../pattemplate/patTemplate/Function/Img.php | 73 + .../patTemplate/Function/Phphighlight.php | 50 + .../patTemplate/Function/Strip.php | 48 + .../pattemplate/patTemplate/Function/Time.php | 63 + .../patTemplate/Function/index.html | 4 + .../pattemplate/patTemplate/InputFilter.php | 45 + .../InputFilter/ShortModifiers.php | 124 + .../patTemplate/InputFilter/StripComments.php | 54 + .../patTemplate/InputFilter/index.html | 4 + .../pattemplate/patTemplate/Modifier.php | 45 + .../patTemplate/Modifier/Dateformat.php | 53 + .../patTemplate/Modifier/Expression.php | 63 + .../patTemplate/Modifier/HTML/Img.php | 63 + .../patTemplate/Modifier/HTML/index.html | 4 + .../patTemplate/Modifier/Numberformat.php | 53 + .../patTemplate/Modifier/QuoteLatex.php | 50 + .../patTemplate/Modifier/Surround.php | 90 + .../patTemplate/Modifier/Truncate.php | 88 + .../patTemplate/Modifier/Wordwrapper.php | 74 + .../patTemplate/Modifier/index.html | 4 + .../pattemplate/patTemplate/Module.php | 87 + .../pattemplate/patTemplate/OutputCache.php | 44 + .../pattemplate/patTemplate/OutputFilter.php | 47 + .../patTemplate/OutputFilter/BBCode.php | 118 + .../patTemplate/OutputFilter/Gzip.php | 106 + .../patTemplate/OutputFilter/HighlightPhp.php | 51 + .../patTemplate/OutputFilter/PdfLatex.php | 65 + .../OutputFilter/StripWhitespace.php | 54 + .../patTemplate/OutputFilter/Tidy.php | 79 + .../patTemplate/OutputFilter/index.html | 4 + .../pattemplate/patTemplate/Reader.php | 1573 ++++++++++ .../pattemplate/patTemplate/Reader/DB.php | 164 + .../pattemplate/patTemplate/Reader/File.php | 174 ++ .../pattemplate/patTemplate/Reader/IT.php | 233 ++ .../pattemplate/patTemplate/Reader/String.php | 42 + .../pattemplate/patTemplate/Reader/index.html | 4 + .../pattemplate/patTemplate/Stat.php | 70 + .../pattemplate/patTemplate/Stat/File.php | 51 + .../pattemplate/patTemplate/Stat/index.html | 4 + .../pattemplate/patTemplate/TemplateCache.php | 83 + .../patTemplate/TemplateCache/File.php | 118 + .../patTemplate/TemplateCache/MMCache.php | 84 + .../TemplateCache/eAccelerator.php | 83 + .../patTemplate/TemplateCache/index.html | 4 + .../pattemplate/patTemplate/index.html | 4 + 110 files changed, 26471 insertions(+) create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/AX.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Association.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/BigMath.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Consumer.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/CryptUtil.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/DatabaseConnection.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/DiffieHellman.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Discover.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/DumbStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Extension.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/FileStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/HMAC.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Interface.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/KVForm.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/MemcachedStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Message.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/MySQLStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Nonce.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/PAPE.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Parse.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/PostgreSQLStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/SQLStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/SQLiteStore.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/SReg.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/Server.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/ServerRequest.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/TrustRoot.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/URINorm.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/OpenID/index.html create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/HTTPFetcher.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/Manager.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/Misc.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/ParanoidHTTPFetcher.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/ParseHTML.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/PlainHTTPFetcher.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/XML.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/XRDS.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/XRI.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/XRIRes.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/Yadis.php create mode 100644 oss/joomla/trunk/libraries/openid/Auth/Yadis/index.html create mode 100644 oss/joomla/trunk/libraries/openid/Auth/index.html create mode 100644 oss/joomla/trunk/libraries/openid/CHANGELOG create mode 100644 oss/joomla/trunk/libraries/openid/CHANGES-2.1.0 create mode 100644 oss/joomla/trunk/libraries/openid/COPYING create mode 100644 oss/joomla/trunk/libraries/openid/NEWS create mode 100644 oss/joomla/trunk/libraries/openid/README create mode 100644 oss/joomla/trunk/libraries/openid/consumer.php create mode 100644 oss/joomla/trunk/libraries/openid/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patError.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patErrorManager.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Compiler.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/Html.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/XUL.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Alias.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Attribute.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Call.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Globalvar.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Highlight.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Img.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Phphighlight.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Strip.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Time.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/ShortModifiers.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/StripComments.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Dateformat.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Expression.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/Img.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Numberformat.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/QuoteLatex.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Surround.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Truncate.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Wordwrapper.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Module.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputCache.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/BBCode.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Gzip.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/HighlightPhp.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/PdfLatex.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/StripWhitespace.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Tidy.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/DB.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/File.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/IT.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/String.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/File.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/File.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/MMCache.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/eAccelerator.php create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/index.html create mode 100644 oss/joomla/trunk/libraries/pattemplate/patTemplate/index.html diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID.php new file mode 100644 index 00000000..31d3478b --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID.php @@ -0,0 +1,555 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * The library version string + */ +define('Auth_OpenID_VERSION', '2.1.2'); + +/** + * Require the fetcher code. + */ +require_once "Auth/Yadis/PlainHTTPFetcher.php"; +require_once "Auth/Yadis/ParanoidHTTPFetcher.php"; +require_once "Auth/OpenID/BigMath.php"; +require_once "Auth/OpenID/URINorm.php"; + +/** + * Status code returned by the server when the only option is to show + * an error page, since we do not have enough information to redirect + * back to the consumer. The associated value is an error message that + * should be displayed on an HTML error page. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_LOCAL_ERROR', 'local_error'); + +/** + * Status code returned when there is an error to return in key-value + * form to the consumer. The caller should return a 400 Bad Request + * response with content-type text/plain and the value as the body. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_REMOTE_ERROR', 'remote_error'); + +/** + * Status code returned when there is a key-value form OK response to + * the consumer. The value associated with this code is the + * response. The caller should return a 200 OK response with + * content-type text/plain and the value as the body. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_REMOTE_OK', 'remote_ok'); + +/** + * Status code returned when there is a redirect back to the + * consumer. The value is the URL to redirect back to. The caller + * should return a 302 Found redirect with a Location: header + * containing the URL. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_REDIRECT', 'redirect'); + +/** + * Status code returned when the caller needs to authenticate the + * user. The associated value is a {@link Auth_OpenID_ServerRequest} + * object that can be used to complete the authentication. If the user + * has taken some authentication action, use the retry() method of the + * {@link Auth_OpenID_ServerRequest} object to complete the request. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_DO_AUTH', 'do_auth'); + +/** + * Status code returned when there were no OpenID arguments + * passed. This code indicates that the caller should return a 200 OK + * response and display an HTML page that says that this is an OpenID + * server endpoint. + * + * @see Auth_OpenID_Server + */ +define('Auth_OpenID_DO_ABOUT', 'do_about'); + +/** + * Defines for regexes and format checking. + */ +define('Auth_OpenID_letters', + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +define('Auth_OpenID_digits', + "0123456789"); + +define('Auth_OpenID_punct', + "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"); + +if (Auth_OpenID_getMathLib() === null) { + Auth_OpenID_setNoMathSupport(); +} + +/** + * The OpenID utility function class. + * + * @package OpenID + * @access private + */ +class Auth_OpenID { + + /** + * Return true if $thing is an Auth_OpenID_FailureResponse object; + * false if not. + * + * @access private + */ + function isFailure($thing) + { + return is_a($thing, 'Auth_OpenID_FailureResponse'); + } + + /** + * Gets the query data from the server environment based on the + * request method used. If GET was used, this looks at + * $_SERVER['QUERY_STRING'] directly. If POST was used, this + * fetches data from the special php://input file stream. + * + * Returns an associative array of the query arguments. + * + * Skips invalid key/value pairs (i.e. keys with no '=value' + * portion). + * + * Returns an empty array if neither GET nor POST was used, or if + * POST was used but php://input cannot be opened. + * + * @access private + */ + function getQuery($query_str=null) + { + $data = array(); + + if ($query_str !== null) { + $data = Auth_OpenID::params_from_string($query_str); + } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) { + // Do nothing. + } else { + // XXX HACK FIXME HORRIBLE. + // + // POSTing to a URL with query parameters is acceptable, but + // we don't have a clean way to distinguish those parameters + // when we need to do things like return_to verification + // which only want to look at one kind of parameter. We're + // going to emulate the behavior of some other environments + // by defaulting to GET and overwriting with POST if POST + // data is available. + $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $str = file_get_contents('php://input'); + + if ($str === false) { + $post = array(); + } else { + $post = Auth_OpenID::params_from_string($str); + } + + $data = array_merge($data, $post); + } + } + + return $data; + } + + function params_from_string($str) + { + $chunks = explode("&", $str); + + $data = array(); + foreach ($chunks as $chunk) { + $parts = explode("=", $chunk, 2); + + if (count($parts) != 2) { + continue; + } + + list($k, $v) = $parts; + $data[$k] = urldecode($v); + } + + return $data; + } + + /** + * Create dir_name as a directory if it does not exist. If it + * exists, make sure that it is, in fact, a directory. Returns + * true if the operation succeeded; false if not. + * + * @access private + */ + function ensureDir($dir_name) + { + if (is_dir($dir_name) || @mkdir($dir_name)) { + return true; + } else { + $parent_dir = dirname($dir_name); + + // Terminal case; there is no parent directory to create. + if ($parent_dir == $dir_name) { + return true; + } + + return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name)); + } + } + + /** + * Adds a string prefix to all values of an array. Returns a new + * array containing the prefixed values. + * + * @access private + */ + function addPrefix($values, $prefix) + { + $new_values = array(); + foreach ($values as $s) { + $new_values[] = $prefix . $s; + } + return $new_values; + } + + /** + * Convenience function for getting array values. Given an array + * $arr and a key $key, get the corresponding value from the array + * or return $default if the key is absent. + * + * @access private + */ + function arrayGet($arr, $key, $fallback = null) + { + if (is_array($arr)) { + if (array_key_exists($key, $arr)) { + return $arr[$key]; + } else { + return $fallback; + } + } else { + trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " . + "array as first parameter, got " . + gettype($arr), E_USER_WARNING); + + return false; + } + } + + /** + * Replacement for PHP's broken parse_str. + */ + function parse_str($query) + { + if ($query === null) { + return null; + } + + $parts = explode('&', $query); + + $new_parts = array(); + for ($i = 0; $i < count($parts); $i++) { + $pair = explode('=', $parts[$i]); + + if (count($pair) != 2) { + continue; + } + + list($key, $value) = $pair; + $new_parts[$key] = urldecode($value); + } + + return $new_parts; + } + + /** + * Implements the PHP 5 'http_build_query' functionality. + * + * @access private + * @param array $data Either an array key/value pairs or an array + * of arrays, each of which holding two values: a key and a value, + * sequentially. + * @return string $result The result of url-encoding the key/value + * pairs from $data into a URL query string + * (e.g. "username=bob&id=56"). + */ + function httpBuildQuery($data) + { + $pairs = array(); + foreach ($data as $key => $value) { + if (is_array($value)) { + $pairs[] = urlencode($value[0])."=".urlencode($value[1]); + } else { + $pairs[] = urlencode($key)."=".urlencode($value); + } + } + return implode("&", $pairs); + } + + /** + * "Appends" query arguments onto a URL. The URL may or may not + * already have arguments (following a question mark). + * + * @access private + * @param string $url A URL, which may or may not already have + * arguments. + * @param array $args Either an array key/value pairs or an array of + * arrays, each of which holding two values: a key and a value, + * sequentially. If $args is an ordinary key/value array, the + * parameters will be added to the URL in sorted alphabetical order; + * if $args is an array of arrays, their order will be preserved. + * @return string $url The original URL with the new parameters added. + * + */ + function appendArgs($url, $args) + { + if (count($args) == 0) { + return $url; + } + + // Non-empty array; if it is an array of arrays, use + // multisort; otherwise use sort. + if (array_key_exists(0, $args) && + is_array($args[0])) { + // Do nothing here. + } else { + $keys = array_keys($args); + sort($keys); + $new_args = array(); + foreach ($keys as $key) { + $new_args[] = array($key, $args[$key]); + } + $args = $new_args; + } + + $sep = '?'; + if (strpos($url, '?') !== false) { + $sep = '&'; + } + + return $url . $sep . Auth_OpenID::httpBuildQuery($args); + } + + /** + * Implements python's urlunparse, which is not available in PHP. + * Given the specified components of a URL, this function rebuilds + * and returns the URL. + * + * @access private + * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'. + * @param string $host The host. Required. + * @param string $port The port. + * @param string $path The path. + * @param string $query The query. + * @param string $fragment The fragment. + * @return string $url The URL resulting from assembling the + * specified components. + */ + function urlunparse($scheme, $host, $port = null, $path = '/', + $query = '', $fragment = '') + { + + if (!$scheme) { + $scheme = 'http'; + } + + if (!$host) { + return false; + } + + if (!$path) { + $path = ''; + } + + $result = $scheme . "://" . $host; + + if ($port) { + $result .= ":" . $port; + } + + $result .= $path; + + if ($query) { + $result .= "?" . $query; + } + + if ($fragment) { + $result .= "#" . $fragment; + } + + return $result; + } + + /** + * Given a URL, this "normalizes" it by adding a trailing slash + * and / or a leading http:// scheme where necessary. Returns + * null if the original URL is malformed and cannot be normalized. + * + * @access private + * @param string $url The URL to be normalized. + * @return mixed $new_url The URL after normalization, or null if + * $url was malformed. + */ + function normalizeUrl($url) + { + @$parsed = parse_url($url); + + if (!$parsed) { + return null; + } + + if (isset($parsed['scheme']) && + isset($parsed['host'])) { + $scheme = strtolower($parsed['scheme']); + if (!in_array($scheme, array('http', 'https'))) { + return null; + } + } else { + $url = 'http://' . $url; + } + + $normalized = Auth_OpenID_urinorm($url); + if ($normalized === null) { + return null; + } + list($defragged, $frag) = Auth_OpenID::urldefrag($normalized); + return $defragged; + } + + /** + * Replacement (wrapper) for PHP's intval() because it's broken. + * + * @access private + */ + function intval($value) + { + $re = "/^\\d+$/"; + + if (!preg_match($re, $value)) { + return false; + } + + return intval($value); + } + + /** + * Count the number of bytes in a string independently of + * multibyte support conditions. + * + * @param string $str The string of bytes to count. + * @return int The number of bytes in $str. + */ + function bytes($str) + { + return strlen(bin2hex($str)) / 2; + } + + /** + * Get the bytes in a string independently of multibyte support + * conditions. + */ + function toBytes($str) + { + $hex = bin2hex($str); + + if (!$hex) { + return array(); + } + + $b = array(); + for ($i = 0; $i < strlen($hex); $i += 2) { + $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10)); + } + + return $b; + } + + function urldefrag($url) + { + $parts = explode("#", $url, 2); + + if (count($parts) == 1) { + return array($parts[0], ""); + } else { + return $parts; + } + } + + function filter($callback, &$sequence) + { + $result = array(); + + foreach ($sequence as $item) { + if (call_user_func_array($callback, array($item))) { + $result[] = $item; + } + } + + return $result; + } + + function update(&$dest, &$src) + { + foreach ($src as $k => $v) { + $dest[$k] = $v; + } + } + + /** + * Wrap PHP's standard error_log functionality. Use this to + * perform all logging. It will interpolate any additional + * arguments into the format string before logging. + * + * @param string $format_string The sprintf format for the message + */ + function log($format_string) + { + $args = func_get_args(); + $message = call_user_func_array('sprintf', $args); + error_log($message); + } + + function autoSubmitHTML($form, $title="OpenId transaction in progress") + { + return("". + "". + $title . + "". + "". + $form . + "". + "". + ""); + } +} +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/AX.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/AX.php new file mode 100644 index 00000000..df3bdb09 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/AX.php @@ -0,0 +1,1026 @@ +message = $message; + } +} + +/** + * Abstract class containing common code for attribute exchange + * messages. + * + * @package OpenID + */ +class Auth_OpenID_AX_Message extends Auth_OpenID_Extension { + /** + * ns_alias: The preferred namespace alias for attribute exchange + * messages + */ + var $ns_alias = 'ax'; + + /** + * mode: The type of this attribute exchange message. This must be + * overridden in subclasses. + */ + var $mode = null; + + var $ns_uri = Auth_OpenID_AX_NS_URI; + + /** + * Return Auth_OpenID_AX_Error if the mode in the attribute + * exchange arguments does not match what is expected for this + * class; true otherwise. + * + * @access private + */ + function _checkMode($ax_args) + { + $mode = Auth_OpenID::arrayGet($ax_args, 'mode'); + if ($mode != $this->mode) { + return new Auth_OpenID_AX_Error( + sprintf( + "Expected mode '%s'; got '%s'", + $this->mode, $mode)); + } + + return true; + } + + /** + * Return a set of attribute exchange arguments containing the + * basic information that must be in every attribute exchange + * message. + * + * @access private + */ + function _newArgs() + { + return array('mode' => $this->mode); + } +} + +/** + * Represents a single attribute in an attribute exchange + * request. This should be added to an AXRequest object in order to + * request the attribute. + * + * @package OpenID + */ +class Auth_OpenID_AX_AttrInfo { + /** + * Construct an attribute information object. Do not call this + * directly; call make(...) instead. + * + * @param string $type_uri The type URI for this attribute. + * + * @param int $count The number of values of this type to request. + * + * @param bool $required Whether the attribute will be marked as + * required in the request. + * + * @param string $alias The name that should be given to this + * attribute in the request. + */ + function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required, + $alias) + { + /** + * required: Whether the attribute will be marked as required + * when presented to the subject of the attribute exchange + * request. + */ + $this->required = $required; + + /** + * count: How many values of this type to request from the + * subject. Defaults to one. + */ + $this->count = $count; + + /** + * type_uri: The identifier that determines what the attribute + * represents and how it is serialized. For example, one type + * URI representing dates could represent a Unix timestamp in + * base 10 and another could represent a human-readable + * string. + */ + $this->type_uri = $type_uri; + + /** + * alias: The name that should be given to this attribute in + * the request. If it is not supplied, a generic name will be + * assigned. For example, if you want to call a Unix timestamp + * value 'tstamp', set its alias to that value. If two + * attributes in the same message request to use the same + * alias, the request will fail to be generated. + */ + $this->alias = $alias; + } + + /** + * Construct an attribute information object. For parameter + * details, see the constructor. + */ + function make($type_uri, $count=1, $required=false, + $alias=null) + { + if ($alias !== null) { + $result = Auth_OpenID_AX_checkAlias($alias); + + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + } + + return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required, + $alias); + } + + /** + * When processing a request for this attribute, the OP should + * call this method to determine whether all available attribute + * values were requested. If self.count == UNLIMITED_VALUES, this + * returns True. Otherwise this returns False, in which case + * self.count is an integer. + */ + function wantsUnlimitedValues() + { + return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES; + } +} + +/** + * Given a namespace mapping and a string containing a comma-separated + * list of namespace aliases, return a list of type URIs that + * correspond to those aliases. + * + * @param $namespace_map The mapping from namespace URI to alias + * @param $alias_list_s The string containing the comma-separated + * list of aliases. May also be None for convenience. + * + * @return $seq The list of namespace URIs that corresponds to the + * supplied list of aliases. If the string was zero-length or None, an + * empty list will be returned. + * + * return null If an alias is present in the list of aliases but + * is not present in the namespace map. + */ +function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s) +{ + $uris = array(); + + if ($alias_list_s) { + foreach (explode(',', $alias_list_s) as $alias) { + $type_uri = $namespace_map->getNamespaceURI($alias); + if ($type_uri === null) { + // raise KeyError( + // 'No type is defined for attribute name %r' % (alias,)) + return new Auth_OpenID_AX_Error( + sprintf('No type is defined for attribute name %s', + $alias) + ); + } else { + $uris[] = $type_uri; + } + } + } + + return $uris; +} + +/** + * An attribute exchange 'fetch_request' message. This message is sent + * by a relying party when it wishes to obtain attributes about the + * subject of an OpenID authentication request. + * + * @package OpenID + */ +class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message { + + var $mode = 'fetch_request'; + + function Auth_OpenID_AX_FetchRequest($update_url=null) + { + /** + * requested_attributes: The attributes that have been + * requested thus far, indexed by the type URI. + */ + $this->requested_attributes = array(); + + /** + * update_url: A URL that will accept responses for this + * attribute exchange request, even in the absence of the user + * who made this request. + */ + $this->update_url = $update_url; + } + + /** + * Add an attribute to this attribute exchange request. + * + * @param attribute: The attribute that is being requested + * @return true on success, false when the requested attribute is + * already present in this fetch request. + */ + function add($attribute) + { + if ($this->contains($attribute->type_uri)) { + return new Auth_OpenID_AX_Error( + sprintf("The attribute %s has already been requested", + $attribute->type_uri)); + } + + $this->requested_attributes[$attribute->type_uri] = $attribute; + + return true; + } + + /** + * Get the serialized form of this attribute fetch request. + * + * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters + */ + function getExtensionArgs() + { + $aliases = new Auth_OpenID_NamespaceMap(); + + $required = array(); + $if_available = array(); + + $ax_args = $this->_newArgs(); + + foreach ($this->requested_attributes as $type_uri => $attribute) { + if ($attribute->alias === null) { + $alias = $aliases->add($type_uri); + } else { + $alias = $aliases->addAlias($type_uri, $attribute->alias); + + if ($alias === null) { + return new Auth_OpenID_AX_Error( + sprintf("Could not add alias %s for URI %s", + $attribute->alias, $type_uri + )); + } + } + + if ($attribute->required) { + $required[] = $alias; + } else { + $if_available[] = $alias; + } + + if ($attribute->count != 1) { + $ax_args['count.' . $alias] = strval($attribute->count); + } + + $ax_args['type.' . $alias] = $type_uri; + } + + if ($required) { + $ax_args['required'] = implode(',', $required); + } + + if ($if_available) { + $ax_args['if_available'] = implode(',', $if_available); + } + + return $ax_args; + } + + /** + * Get the type URIs for all attributes that have been marked as + * required. + * + * @return A list of the type URIs for attributes that have been + * marked as required. + */ + function getRequiredAttrs() + { + $required = array(); + foreach ($this->requested_attributes as $type_uri => $attribute) { + if ($attribute->required) { + $required[] = $type_uri; + } + } + + return $required; + } + + /** + * Extract a FetchRequest from an OpenID message + * + * @param request: The OpenID request containing the attribute + * fetch request + * + * @returns mixed An Auth_OpenID_AX_Error or the + * Auth_OpenID_AX_FetchRequest extracted from the request message if + * successful + */ + function &fromOpenIDRequest($request) + { + $m = $request->message; + $obj = new Auth_OpenID_AX_FetchRequest(); + $ax_args = $m->getArgs($obj->ns_uri); + + $result = $obj->parseExtensionArgs($ax_args); + + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + + if ($obj->update_url) { + // Update URL must match the openid.realm of the + // underlying OpenID 2 message. + $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm', + $m->getArg( + Auth_OpenID_OPENID_NS, + 'return_to')); + + if (!$realm) { + $obj = new Auth_OpenID_AX_Error( + sprintf("Cannot validate update_url %s " . + "against absent realm", $obj->update_url)); + } else if (!Auth_OpenID_TrustRoot::match($realm, + $obj->update_url)) { + $obj = new Auth_OpenID_AX_Error( + sprintf("Update URL %s failed validation against realm %s", + $obj->update_url, $realm)); + } + } + + return $obj; + } + + /** + * Given attribute exchange arguments, populate this FetchRequest. + * + * @return $result Auth_OpenID_AX_Error if the data to be parsed + * does not follow the attribute exchange specification. At least + * when 'if_available' or 'required' is not specified for a + * particular attribute type. Returns true otherwise. + */ + function parseExtensionArgs($ax_args) + { + $result = $this->_checkMode($ax_args); + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + + $aliases = new Auth_OpenID_NamespaceMap(); + + foreach ($ax_args as $key => $value) { + if (strpos($key, 'type.') === 0) { + $alias = substr($key, 5); + $type_uri = $value; + + $alias = $aliases->addAlias($type_uri, $alias); + + if ($alias === null) { + return new Auth_OpenID_AX_Error( + sprintf("Could not add alias %s for URI %s", + $alias, $type_uri) + ); + } + + $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias); + if ($count_s) { + $count = Auth_OpenID::intval($count_s); + if (($count === false) && + ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) { + $count = $count_s; + } + } else { + $count = 1; + } + + if ($count === false) { + return new Auth_OpenID_AX_Error( + sprintf("Integer value expected for %s, got %s", + 'count.' . $alias, $count_s)); + } + + $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count, + false, $alias); + + if (Auth_OpenID_AX::isError($attrinfo)) { + return $attrinfo; + } + + $this->add($attrinfo); + } + } + + $required = Auth_OpenID_AX_toTypeURIs($aliases, + Auth_OpenID::arrayGet($ax_args, 'required')); + + foreach ($required as $type_uri) { + $attrib =& $this->requested_attributes[$type_uri]; + $attrib->required = true; + } + + $if_available = Auth_OpenID_AX_toTypeURIs($aliases, + Auth_OpenID::arrayGet($ax_args, 'if_available')); + + $all_type_uris = array_merge($required, $if_available); + + foreach ($aliases->iterNamespaceURIs() as $type_uri) { + if (!in_array($type_uri, $all_type_uris)) { + return new Auth_OpenID_AX_Error( + sprintf('Type URI %s was in the request but not ' . + 'present in "required" or "if_available"', + $type_uri)); + + } + } + + $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url'); + + return true; + } + + /** + * Iterate over the AttrInfo objects that are contained in this + * fetch_request. + */ + function iterAttrs() + { + return array_values($this->requested_attributes); + } + + function iterTypes() + { + return array_keys($this->requested_attributes); + } + + /** + * Is the given type URI present in this fetch_request? + */ + function contains($type_uri) + { + return in_array($type_uri, $this->iterTypes()); + } +} + +/** + * An abstract class that implements a message that has attribute keys + * and values. It contains the common code between fetch_response and + * store_request. + * + * @package OpenID + */ +class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message { + + function Auth_OpenID_AX_KeyValueMessage() + { + $this->data = array(); + } + + /** + * Add a single value for the given attribute type to the + * message. If there are already values specified for this type, + * this value will be sent in addition to the values already + * specified. + * + * @param type_uri: The URI for the attribute + * @param value: The value to add to the response to the relying + * party for this attribute + * @return null + */ + function addValue($type_uri, $value) + { + if (!array_key_exists($type_uri, $this->data)) { + $this->data[$type_uri] = array(); + } + + $values =& $this->data[$type_uri]; + $values[] = $value; + } + + /** + * Set the values for the given attribute type. This replaces any + * values that have already been set for this attribute. + * + * @param type_uri: The URI for the attribute + * @param values: A list of values to send for this attribute. + */ + function setValues($type_uri, &$values) + { + $this->data[$type_uri] =& $values; + } + + /** + * Get the extension arguments for the key/value pairs contained + * in this message. + * + * @param aliases: An alias mapping. Set to None if you don't care + * about the aliases for this request. + * + * @access private + */ + function _getExtensionKVArgs(&$aliases) + { + if ($aliases === null) { + $aliases = new Auth_OpenID_NamespaceMap(); + } + + $ax_args = array(); + + foreach ($this->data as $type_uri => $values) { + $alias = $aliases->add($type_uri); + + $ax_args['type.' . $alias] = $type_uri; + $ax_args['count.' . $alias] = strval(count($values)); + + foreach ($values as $i => $value) { + $key = sprintf('value.%s.%d', $alias, $i + 1); + $ax_args[$key] = $value; + } + } + + return $ax_args; + } + + /** + * Parse attribute exchange key/value arguments into this object. + * + * @param ax_args: The attribute exchange fetch_response + * arguments, with namespacing removed. + * + * @return Auth_OpenID_AX_Error or true + */ + function parseExtensionArgs($ax_args) + { + $result = $this->_checkMode($ax_args); + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + + $aliases = new Auth_OpenID_NamespaceMap(); + + foreach ($ax_args as $key => $value) { + if (strpos($key, 'type.') === 0) { + $type_uri = $value; + $alias = substr($key, 5); + + $result = Auth_OpenID_AX_checkAlias($alias); + + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + + $alias = $aliases->addAlias($type_uri, $alias); + + if ($alias === null) { + return new Auth_OpenID_AX_Error( + sprintf("Could not add alias %s for URI %s", + $alias, $type_uri) + ); + } + } + } + + foreach ($aliases->iteritems() as $pair) { + list($type_uri, $alias) = $pair; + + if (array_key_exists('count.' . $alias, $ax_args)) { + + $count_key = 'count.' . $alias; + $count_s = $ax_args[$count_key]; + + $count = Auth_OpenID::intval($count_s); + + if ($count === false) { + return new Auth_OpenID_AX_Error( + sprintf("Integer value expected for %s, got %s", + 'count. %s' . $alias, $count_s, + Auth_OpenID_AX_UNLIMITED_VALUES) + ); + } + + $values = array(); + for ($i = 1; $i < $count + 1; $i++) { + $value_key = sprintf('value.%s.%d', $alias, $i); + + if (!array_key_exists($value_key, $ax_args)) { + return new Auth_OpenID_AX_Error( + sprintf( + "No value found for key %s", + $value_key)); + } + + $value = $ax_args[$value_key]; + $values[] = $value; + } + } else { + $key = 'value.' . $alias; + + if (!array_key_exists($key, $ax_args)) { + return new Auth_OpenID_AX_Error( + sprintf( + "No value found for key %s", + $key)); + } + + $value = $ax_args['value.' . $alias]; + + if ($value == '') { + $values = array(); + } else { + $values = array($value); + } + } + + $this->data[$type_uri] = $values; + } + + return true; + } + + /** + * Get a single value for an attribute. If no value was sent for + * this attribute, use the supplied default. If there is more than + * one value for this attribute, this method will fail. + * + * @param type_uri: The URI for the attribute + * @param default: The value to return if the attribute was not + * sent in the fetch_response. + * + * @return $value Auth_OpenID_AX_Error on failure or the value of + * the attribute in the fetch_response message, or the default + * supplied + */ + function getSingle($type_uri, $default=null) + { + $values = Auth_OpenID::arrayGet($this->data, $type_uri); + if (!$values) { + return $default; + } else if (count($values) == 1) { + return $values[0]; + } else { + return new Auth_OpenID_AX_Error( + sprintf('More than one value present for %s', + $type_uri) + ); + } + } + + /** + * Get the list of values for this attribute in the + * fetch_response. + * + * XXX: what to do if the values are not present? default + * parameter? this is funny because it's always supposed to return + * a list, so the default may break that, though it's provided by + * the user's code, so it might be okay. If no default is + * supplied, should the return be None or []? + * + * @param type_uri: The URI of the attribute + * + * @return $values The list of values for this attribute in the + * response. May be an empty list. If the attribute was not sent + * in the response, returns Auth_OpenID_AX_Error. + */ + function get($type_uri) + { + if (array_key_exists($type_uri, $this->data)) { + return $this->data[$type_uri]; + } else { + return new Auth_OpenID_AX_Error( + sprintf("Type URI %s not found in response", + $type_uri) + ); + } + } + + /** + * Get the number of responses for a particular attribute in this + * fetch_response message. + * + * @param type_uri: The URI of the attribute + * + * @returns int The number of values sent for this attribute. If + * the attribute was not sent in the response, returns + * Auth_OpenID_AX_Error. + */ + function count($type_uri) + { + if (array_key_exists($type_uri, $this->data)) { + return count($this->get($type_uri)); + } else { + return new Auth_OpenID_AX_Error( + sprintf("Type URI %s not found in response", + $type_uri) + ); + } + } +} + +/** + * A fetch_response attribute exchange message. + * + * @package OpenID + */ +class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage { + var $mode = 'fetch_response'; + + function Auth_OpenID_AX_FetchResponse($update_url=null) + { + $this->Auth_OpenID_AX_KeyValueMessage(); + $this->update_url = $update_url; + } + + /** + * Serialize this object into arguments in the attribute exchange + * namespace + * + * @return $args The dictionary of unqualified attribute exchange + * arguments that represent this fetch_response, or + * Auth_OpenID_AX_Error on error. + */ + function getExtensionArgs($request=null) + { + $aliases = new Auth_OpenID_NamespaceMap(); + + $zero_value_types = array(); + + if ($request !== null) { + // Validate the data in the context of the request (the + // same attributes should be present in each, and the + // counts in the response must be no more than the counts + // in the request) + + foreach ($this->data as $type_uri => $unused) { + if (!$request->contains($type_uri)) { + return new Auth_OpenID_AX_Error( + sprintf("Response attribute not present in request: %s", + $type_uri) + ); + } + } + + foreach ($request->iterAttrs() as $attr_info) { + // Copy the aliases from the request so that reading + // the response in light of the request is easier + if ($attr_info->alias === null) { + $aliases->add($attr_info->type_uri); + } else { + $alias = $aliases->addAlias($attr_info->type_uri, + $attr_info->alias); + + if ($alias === null) { + return new Auth_OpenID_AX_Error( + sprintf("Could not add alias %s for URI %s", + $attr_info->alias, $attr_info->type_uri) + ); + } + } + + if (array_key_exists($attr_info->type_uri, $this->data)) { + $values = $this->data[$attr_info->type_uri]; + } else { + $values = array(); + $zero_value_types[] = $attr_info; + } + + if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) && + ($attr_info->count < count($values))) { + return new Auth_OpenID_AX_Error( + sprintf("More than the number of requested values " . + "were specified for %s", + $attr_info->type_uri) + ); + } + } + } + + $kv_args = $this->_getExtensionKVArgs($aliases); + + // Add the KV args into the response with the args that are + // unique to the fetch_response + $ax_args = $this->_newArgs(); + + // For each requested attribute, put its type/alias and count + // into the response even if no data were returned. + foreach ($zero_value_types as $attr_info) { + $alias = $aliases->getAlias($attr_info->type_uri); + $kv_args['type.' . $alias] = $attr_info->type_uri; + $kv_args['count.' . $alias] = '0'; + } + + $update_url = null; + if ($request) { + $update_url = $request->update_url; + } else { + $update_url = $this->update_url; + } + + if ($update_url) { + $ax_args['update_url'] = $update_url; + } + + Auth_OpenID::update(&$ax_args, $kv_args); + + return $ax_args; + } + + /** + * @return $result Auth_OpenID_AX_Error on failure or true on + * success. + */ + function parseExtensionArgs($ax_args) + { + $result = parent::parseExtensionArgs($ax_args); + + if (Auth_OpenID_AX::isError($result)) { + return $result; + } + + $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url'); + + return true; + } + + /** + * Construct a FetchResponse object from an OpenID library + * SuccessResponse object. + * + * @param success_response: A successful id_res response object + * + * @param signed: Whether non-signed args should be processsed. If + * True (the default), only signed arguments will be processsed. + * + * @return $response A FetchResponse containing the data from the + * OpenID message + */ + function fromSuccessResponse($success_response, $signed=true) + { + $obj = new Auth_OpenID_AX_FetchResponse(); + if ($signed) { + $ax_args = $success_response->getSignedNS($obj->ns_uri); + } else { + $ax_args = $success_response->message->getArgs($obj->ns_uri); + } + if ($ax_args === null || Auth_OpenID::isFailure($ax_args) || + sizeof($ax_args) == 0) { + return null; + } + + $result = $obj->parseExtensionArgs($ax_args); + if (Auth_OpenID_AX::isError($result)) { + #XXX log me + return null; + } + return $obj; + } +} + +/** + * A store request attribute exchange message representation. + * + * @package OpenID + */ +class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage { + var $mode = 'store_request'; + + /** + * @param array $aliases The namespace aliases to use when making + * this store response. Leave as None to use defaults. + */ + function getExtensionArgs($aliases=null) + { + $ax_args = $this->_newArgs(); + $kv_args = $this->_getExtensionKVArgs($aliases); + Auth_OpenID::update(&$ax_args, $kv_args); + return $ax_args; + } +} + +/** + * An indication that the store request was processed along with this + * OpenID transaction. Use make(), NOT the constructor, to create + * response objects. + * + * @package OpenID + */ +class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message { + var $SUCCESS_MODE = 'store_response_success'; + var $FAILURE_MODE = 'store_response_failure'; + + /** + * Returns Auth_OpenID_AX_Error on error or an + * Auth_OpenID_AX_StoreResponse object on success. + */ + function &make($succeeded=true, $error_message=null) + { + if (($succeeded) && ($error_message !== null)) { + return new Auth_OpenID_AX_Error('An error message may only be '. + 'included in a failing fetch response'); + } + + return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message); + } + + function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null) + { + if ($succeeded) { + $this->mode = $this->SUCCESS_MODE; + } else { + $this->mode = $this->FAILURE_MODE; + } + + $this->error_message = $error_message; + } + + /** + * Was this response a success response? + */ + function succeeded() + { + return $this->mode == $this->SUCCESS_MODE; + } + + function getExtensionArgs() + { + $ax_args = $this->_newArgs(); + if ((!$this->succeeded()) && $this->error_message) { + $ax_args['error'] = $this->error_message; + } + + return $ax_args; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Association.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Association.php new file mode 100644 index 00000000..42b2fadf --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Association.php @@ -0,0 +1,616 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * @access private + */ +require_once 'Auth/OpenID/CryptUtil.php'; + +/** + * @access private + */ +require_once 'Auth/OpenID/KVForm.php'; + +/** + * @access private + */ +require_once 'Auth/OpenID/HMAC.php'; + +/** + * This class represents an association between a server and a + * consumer. In general, users of this library will never see + * instances of this object. The only exception is if you implement a + * custom {@link Auth_OpenID_OpenIDStore}. + * + * If you do implement such a store, it will need to store the values + * of the handle, secret, issued, lifetime, and assoc_type instance + * variables. + * + * @package OpenID + */ +class Auth_OpenID_Association { + + /** + * This is a HMAC-SHA1 specific value. + * + * @access private + */ + var $SIG_LENGTH = 20; + + /** + * The ordering and name of keys as stored by serialize. + * + * @access private + */ + var $assoc_keys = array( + 'version', + 'handle', + 'secret', + 'issued', + 'lifetime', + 'assoc_type' + ); + + var $_macs = array( + 'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1', + 'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256' + ); + + /** + * This is an alternate constructor (factory method) used by the + * OpenID consumer library to create associations. OpenID store + * implementations shouldn't use this constructor. + * + * @access private + * + * @param integer $expires_in This is the amount of time this + * association is good for, measured in seconds since the + * association was issued. + * + * @param string $handle This is the handle the server gave this + * association. + * + * @param string secret This is the shared secret the server + * generated for this association. + * + * @param assoc_type This is the type of association this + * instance represents. The only valid values of this field at + * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may + * be defined in the future. + * + * @return association An {@link Auth_OpenID_Association} + * instance. + */ + function fromExpiresIn($expires_in, $handle, $secret, $assoc_type) + { + $issued = time(); + $lifetime = $expires_in; + return new Auth_OpenID_Association($handle, $secret, + $issued, $lifetime, $assoc_type); + } + + /** + * This is the standard constructor for creating an association. + * The library should create all of the necessary associations, so + * this constructor is not part of the external API. + * + * @access private + * + * @param string $handle This is the handle the server gave this + * association. + * + * @param string $secret This is the shared secret the server + * generated for this association. + * + * @param integer $issued This is the time this association was + * issued, in seconds since 00:00 GMT, January 1, 1970. (ie, a + * unix timestamp) + * + * @param integer $lifetime This is the amount of time this + * association is good for, measured in seconds since the + * association was issued. + * + * @param string $assoc_type This is the type of association this + * instance represents. The only valid values of this field at + * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may + * be defined in the future. + */ + function Auth_OpenID_Association( + $handle, $secret, $issued, $lifetime, $assoc_type) + { + if (!in_array($assoc_type, + Auth_OpenID_getSupportedAssociationTypes())) { + $fmt = 'Unsupported association type (%s)'; + trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR); + } + + $this->handle = $handle; + $this->secret = $secret; + $this->issued = $issued; + $this->lifetime = $lifetime; + $this->assoc_type = $assoc_type; + } + + /** + * This returns the number of seconds this association is still + * valid for, or 0 if the association is no longer valid. + * + * @return integer $seconds The number of seconds this association + * is still valid for, or 0 if the association is no longer valid. + */ + function getExpiresIn($now = null) + { + if ($now == null) { + $now = time(); + } + + return max(0, $this->issued + $this->lifetime - $now); + } + + /** + * This checks to see if two {@link Auth_OpenID_Association} + * instances represent the same association. + * + * @return bool $result true if the two instances represent the + * same association, false otherwise. + */ + function equal($other) + { + return ((gettype($this) == gettype($other)) + && ($this->handle == $other->handle) + && ($this->secret == $other->secret) + && ($this->issued == $other->issued) + && ($this->lifetime == $other->lifetime) + && ($this->assoc_type == $other->assoc_type)); + } + + /** + * Convert an association to KV form. + * + * @return string $result String in KV form suitable for + * deserialization by deserialize. + */ + function serialize() + { + $data = array( + 'version' => '2', + 'handle' => $this->handle, + 'secret' => base64_encode($this->secret), + 'issued' => strval(intval($this->issued)), + 'lifetime' => strval(intval($this->lifetime)), + 'assoc_type' => $this->assoc_type + ); + + assert(array_keys($data) == $this->assoc_keys); + + return Auth_OpenID_KVForm::fromArray($data, $strict = true); + } + + /** + * Parse an association as stored by serialize(). This is the + * inverse of serialize. + * + * @param string $assoc_s Association as serialized by serialize() + * @return Auth_OpenID_Association $result instance of this class + */ + function deserialize($class_name, $assoc_s) + { + $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true); + $keys = array(); + $values = array(); + foreach ($pairs as $key => $value) { + if (is_array($value)) { + list($key, $value) = $value; + } + $keys[] = $key; + $values[] = $value; + } + + $class_vars = get_class_vars($class_name); + $class_assoc_keys = $class_vars['assoc_keys']; + + sort($keys); + sort($class_assoc_keys); + + if ($keys != $class_assoc_keys) { + trigger_error('Unexpected key values: ' . var_export($keys, true), + E_USER_WARNING); + return null; + } + + $version = $pairs['version']; + $handle = $pairs['handle']; + $secret = $pairs['secret']; + $issued = $pairs['issued']; + $lifetime = $pairs['lifetime']; + $assoc_type = $pairs['assoc_type']; + + if ($version != '2') { + trigger_error('Unknown version: ' . $version, E_USER_WARNING); + return null; + } + + $issued = intval($issued); + $lifetime = intval($lifetime); + $secret = base64_decode($secret); + + return new $class_name( + $handle, $secret, $issued, $lifetime, $assoc_type); + } + + /** + * Generate a signature for a sequence of (key, value) pairs + * + * @access private + * @param array $pairs The pairs to sign, in order. This is an + * array of two-tuples. + * @return string $signature The binary signature of this sequence + * of pairs + */ + function sign($pairs) + { + $kv = Auth_OpenID_KVForm::fromArray($pairs); + + /* Invalid association types should be caught at constructor */ + $callback = $this->_macs[$this->assoc_type]; + + return call_user_func_array($callback, array($this->secret, $kv)); + } + + /** + * Generate a signature for some fields in a dictionary + * + * @access private + * @param array $fields The fields to sign, in order; this is an + * array of strings. + * @param array $data Dictionary of values to sign (an array of + * string => string pairs). + * @return string $signature The signature, base64 encoded + */ + function signMessage($message) + { + if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') || + $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) { + // Already has a sig + return null; + } + + $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS, + 'assoc_handle'); + + if ($extant_handle && ($extant_handle != $this->handle)) { + // raise ValueError("Message has a different association handle") + return null; + } + + $signed_message = $message; + $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', + $this->handle); + + $message_keys = array_keys($signed_message->toPostArgs()); + $signed_list = array(); + $signed_prefix = 'openid.'; + + foreach ($message_keys as $k) { + if (strpos($k, $signed_prefix) === 0) { + $signed_list[] = substr($k, strlen($signed_prefix)); + } + } + + $signed_list[] = 'signed'; + sort($signed_list); + + $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed', + implode(',', $signed_list)); + $sig = $this->getMessageSignature($signed_message); + $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig); + return $signed_message; + } + + /** + * Given a {@link Auth_OpenID_Message}, return the key/value pairs + * to be signed according to the signed list in the message. If + * the message lacks a signed list, return null. + * + * @access private + */ + function _makePairs(&$message) + { + $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed'); + if (!$signed || Auth_OpenID::isFailure($signed)) { + // raise ValueError('Message has no signed list: %s' % (message,)) + return null; + } + + $signed_list = explode(',', $signed); + $pairs = array(); + $data = $message->toPostArgs(); + foreach ($signed_list as $field) { + $pairs[] = array($field, Auth_OpenID::arrayGet($data, + 'openid.' . + $field, '')); + } + return $pairs; + } + + /** + * Given an {@link Auth_OpenID_Message}, return the signature for + * the signed list in the message. + * + * @access private + */ + function getMessageSignature(&$message) + { + $pairs = $this->_makePairs($message); + return base64_encode($this->sign($pairs)); + } + + /** + * Confirm that the signature of these fields matches the + * signature contained in the data. + * + * @access private + */ + function checkMessageSignature(&$message) + { + $sig = $message->getArg(Auth_OpenID_OPENID_NS, + 'sig'); + + if (!$sig || Auth_OpenID::isFailure($sig)) { + return false; + } + + $calculated_sig = $this->getMessageSignature($message); + return $calculated_sig == $sig; + } +} + +function Auth_OpenID_getSecretSize($assoc_type) +{ + if ($assoc_type == 'HMAC-SHA1') { + return 20; + } else if ($assoc_type == 'HMAC-SHA256') { + return 32; + } else { + return null; + } +} + +function Auth_OpenID_getAllAssociationTypes() +{ + return array('HMAC-SHA1', 'HMAC-SHA256'); +} + +function Auth_OpenID_getSupportedAssociationTypes() +{ + $a = array('HMAC-SHA1'); + + if (Auth_OpenID_HMACSHA256_SUPPORTED) { + $a[] = 'HMAC-SHA256'; + } + + return $a; +} + +function Auth_OpenID_getSessionTypes($assoc_type) +{ + $assoc_to_session = array( + 'HMAC-SHA1' => array('DH-SHA1', 'no-encryption')); + + if (Auth_OpenID_HMACSHA256_SUPPORTED) { + $assoc_to_session['HMAC-SHA256'] = + array('DH-SHA256', 'no-encryption'); + } + + return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array()); +} + +function Auth_OpenID_checkSessionType($assoc_type, $session_type) +{ + if (!in_array($session_type, + Auth_OpenID_getSessionTypes($assoc_type))) { + return false; + } + + return true; +} + +function Auth_OpenID_getDefaultAssociationOrder() +{ + $order = array(); + + if (!Auth_OpenID_noMathSupport()) { + $order[] = array('HMAC-SHA1', 'DH-SHA1'); + + if (Auth_OpenID_HMACSHA256_SUPPORTED) { + $order[] = array('HMAC-SHA256', 'DH-SHA256'); + } + } + + $order[] = array('HMAC-SHA1', 'no-encryption'); + + if (Auth_OpenID_HMACSHA256_SUPPORTED) { + $order[] = array('HMAC-SHA256', 'no-encryption'); + } + + return $order; +} + +function Auth_OpenID_getOnlyEncryptedOrder() +{ + $result = array(); + + foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) { + list($assoc, $session) = $pair; + + if ($session != 'no-encryption') { + if (Auth_OpenID_HMACSHA256_SUPPORTED && + ($assoc == 'HMAC-SHA256')) { + $result[] = $pair; + } else if ($assoc != 'HMAC-SHA256') { + $result[] = $pair; + } + } + } + + return $result; +} + +function &Auth_OpenID_getDefaultNegotiator() +{ + $x = new Auth_OpenID_SessionNegotiator( + Auth_OpenID_getDefaultAssociationOrder()); + return $x; +} + +function &Auth_OpenID_getEncryptedNegotiator() +{ + $x = new Auth_OpenID_SessionNegotiator( + Auth_OpenID_getOnlyEncryptedOrder()); + return $x; +} + +/** + * A session negotiator controls the allowed and preferred association + * types and association session types. Both the {@link + * Auth_OpenID_Consumer} and {@link Auth_OpenID_Server} use + * negotiators when creating associations. + * + * You can create and use negotiators if you: + + * - Do not want to do Diffie-Hellman key exchange because you use + * transport-layer encryption (e.g. SSL) + * + * - Want to use only SHA-256 associations + * + * - Do not want to support plain-text associations over a non-secure + * channel + * + * It is up to you to set a policy for what kinds of associations to + * accept. By default, the library will make any kind of association + * that is allowed in the OpenID 2.0 specification. + * + * Use of negotiators in the library + * ================================= + * + * When a consumer makes an association request, it calls {@link + * getAllowedType} to get the preferred association type and + * association session type. + * + * The server gets a request for a particular association/session type + * and calls {@link isAllowed} to determine if it should create an + * association. If it is supported, negotiation is complete. If it is + * not, the server calls {@link getAllowedType} to get an allowed + * association type to return to the consumer. + * + * If the consumer gets an error response indicating that the + * requested association/session type is not supported by the server + * that contains an assocation/session type to try, it calls {@link + * isAllowed} to determine if it should try again with the given + * combination of association/session type. + * + * @package OpenID + */ +class Auth_OpenID_SessionNegotiator { + function Auth_OpenID_SessionNegotiator($allowed_types) + { + $this->allowed_types = array(); + $this->setAllowedTypes($allowed_types); + } + + /** + * Set the allowed association types, checking to make sure each + * combination is valid. + * + * @access private + */ + function setAllowedTypes($allowed_types) + { + foreach ($allowed_types as $pair) { + list($assoc_type, $session_type) = $pair; + if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) { + return false; + } + } + + $this->allowed_types = $allowed_types; + return true; + } + + /** + * Add an association type and session type to the allowed types + * list. The assocation/session pairs are tried in the order that + * they are added. + * + * @access private + */ + function addAllowedType($assoc_type, $session_type = null) + { + if ($this->allowed_types === null) { + $this->allowed_types = array(); + } + + if ($session_type === null) { + $available = Auth_OpenID_getSessionTypes($assoc_type); + + if (!$available) { + return false; + } + + foreach ($available as $session_type) { + $this->addAllowedType($assoc_type, $session_type); + } + } else { + if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) { + $this->allowed_types[] = array($assoc_type, $session_type); + } else { + return false; + } + } + + return true; + } + + // Is this combination of association type and session type allowed? + function isAllowed($assoc_type, $session_type) + { + $assoc_good = in_array(array($assoc_type, $session_type), + $this->allowed_types); + + $matches = in_array($session_type, + Auth_OpenID_getSessionTypes($assoc_type)); + + return ($assoc_good && $matches); + } + + /** + * Get a pair of assocation type and session type that are + * supported. + */ + function getAllowedType() + { + if (!$this->allowed_types) { + return array(null, null); + } + + return $this->allowed_types[0]; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/BigMath.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/BigMath.php new file mode 100644 index 00000000..a91326d4 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/BigMath.php @@ -0,0 +1,474 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Needed for random number generation + */ +require_once 'Auth/OpenID/CryptUtil.php'; + +/** + * Need Auth_OpenID::bytes(). + */ +require_once 'Auth/OpenID.php'; + +/** + * The superclass of all big-integer math implementations + * @access private + * @package OpenID + */ +class Auth_OpenID_MathLibrary { + /** + * Given a long integer, returns the number converted to a binary + * string. This function accepts long integer values of arbitrary + * magnitude and uses the local large-number math library when + * available. + * + * @param integer $long The long number (can be a normal PHP + * integer or a number created by one of the available long number + * libraries) + * @return string $binary The binary version of $long + */ + function longToBinary($long) + { + $cmp = $this->cmp($long, 0); + if ($cmp < 0) { + $msg = __FUNCTION__ . " takes only positive integers."; + trigger_error($msg, E_USER_ERROR); + return null; + } + + if ($cmp == 0) { + return "\x00"; + } + + $bytes = array(); + + while ($this->cmp($long, 0) > 0) { + array_unshift($bytes, $this->mod($long, 256)); + $long = $this->div($long, pow(2, 8)); + } + + if ($bytes && ($bytes[0] > 127)) { + array_unshift($bytes, 0); + } + + $string = ''; + foreach ($bytes as $byte) { + $string .= pack('C', $byte); + } + + return $string; + } + + /** + * Given a binary string, returns the binary string converted to a + * long number. + * + * @param string $binary The binary version of a long number, + * probably as a result of calling longToBinary + * @return integer $long The long number equivalent of the binary + * string $str + */ + function binaryToLong($str) + { + if ($str === null) { + return null; + } + + // Use array_merge to return a zero-indexed array instead of a + // one-indexed array. + $bytes = array_merge(unpack('C*', $str)); + + $n = $this->init(0); + + if ($bytes && ($bytes[0] > 127)) { + trigger_error("bytesToNum works only for positive integers.", + E_USER_WARNING); + return null; + } + + foreach ($bytes as $byte) { + $n = $this->mul($n, pow(2, 8)); + $n = $this->add($n, $byte); + } + + return $n; + } + + function base64ToLong($str) + { + $b64 = base64_decode($str); + + if ($b64 === false) { + return false; + } + + return $this->binaryToLong($b64); + } + + function longToBase64($str) + { + return base64_encode($this->longToBinary($str)); + } + + /** + * Returns a random number in the specified range. This function + * accepts $start, $stop, and $step values of arbitrary magnitude + * and will utilize the local large-number math library when + * available. + * + * @param integer $start The start of the range, or the minimum + * random number to return + * @param integer $stop The end of the range, or the maximum + * random number to return + * @param integer $step The step size, such that $result - ($step + * * N) = $start for some N + * @return integer $result The resulting randomly-generated number + */ + function rand($stop) + { + static $duplicate_cache = array(); + + // Used as the key for the duplicate cache + $rbytes = $this->longToBinary($stop); + + if (array_key_exists($rbytes, $duplicate_cache)) { + list($duplicate, $nbytes) = $duplicate_cache[$rbytes]; + } else { + if ($rbytes[0] == "\x00") { + $nbytes = Auth_OpenID::bytes($rbytes) - 1; + } else { + $nbytes = Auth_OpenID::bytes($rbytes); + } + + $mxrand = $this->pow(256, $nbytes); + + // If we get a number less than this, then it is in the + // duplicated range. + $duplicate = $this->mod($mxrand, $stop); + + if (count($duplicate_cache) > 10) { + $duplicate_cache = array(); + } + + $duplicate_cache[$rbytes] = array($duplicate, $nbytes); + } + + do { + $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes); + $n = $this->binaryToLong($bytes); + // Keep looping if this value is in the low duplicated range + } while ($this->cmp($n, $duplicate) < 0); + + return $this->mod($n, $stop); + } +} + +/** + * Exposes BCmath math library functionality. + * + * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided + * by the BCMath extension. + * + * @access private + * @package OpenID + */ +class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{ + var $type = 'bcmath'; + + function add($x, $y) + { + return bcadd($x, $y); + } + + function sub($x, $y) + { + return bcsub($x, $y); + } + + function pow($base, $exponent) + { + return bcpow($base, $exponent); + } + + function cmp($x, $y) + { + return bccomp($x, $y); + } + + function init($number, $base = 10) + { + return $number; + } + + function mod($base, $modulus) + { + return bcmod($base, $modulus); + } + + function mul($x, $y) + { + return bcmul($x, $y); + } + + function div($x, $y) + { + return bcdiv($x, $y); + } + + /** + * Same as bcpowmod when bcpowmod is missing + * + * @access private + */ + function _powmod($base, $exponent, $modulus) + { + $square = $this->mod($base, $modulus); + $result = 1; + while($this->cmp($exponent, 0) > 0) { + if ($this->mod($exponent, 2)) { + $result = $this->mod($this->mul($result, $square), $modulus); + } + $square = $this->mod($this->mul($square, $square), $modulus); + $exponent = $this->div($exponent, 2); + } + return $result; + } + + function powmod($base, $exponent, $modulus) + { + if (function_exists('bcpowmod')) { + return bcpowmod($base, $exponent, $modulus); + } else { + return $this->_powmod($base, $exponent, $modulus); + } + } + + function toString($num) + { + return $num; + } +} + +/** + * Exposes GMP math library functionality. + * + * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided + * by the GMP extension. + * + * @access private + * @package OpenID + */ +class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{ + var $type = 'gmp'; + + function add($x, $y) + { + return gmp_add($x, $y); + } + + function sub($x, $y) + { + return gmp_sub($x, $y); + } + + function pow($base, $exponent) + { + return gmp_pow($base, $exponent); + } + + function cmp($x, $y) + { + return gmp_cmp($x, $y); + } + + function init($number, $base = 10) + { + return gmp_init($number, $base); + } + + function mod($base, $modulus) + { + return gmp_mod($base, $modulus); + } + + function mul($x, $y) + { + return gmp_mul($x, $y); + } + + function div($x, $y) + { + return gmp_div_q($x, $y); + } + + function powmod($base, $exponent, $modulus) + { + return gmp_powm($base, $exponent, $modulus); + } + + function toString($num) + { + return gmp_strval($num); + } +} + +/** + * Define the supported extensions. An extension array has keys + * 'modules', 'extension', and 'class'. 'modules' is an array of PHP + * module names which the loading code will attempt to load. These + * values will be suffixed with a library file extension (e.g. ".so"). + * 'extension' is the name of a PHP extension which will be tested + * before 'modules' are loaded. 'class' is the string name of a + * {@link Auth_OpenID_MathWrapper} subclass which should be + * instantiated if a given extension is present. + * + * You can define new math library implementations and add them to + * this array. + */ +function Auth_OpenID_math_extensions() +{ + $result = array(); + + if (!defined('Auth_OpenID_BUGGY_GMP')) { + $result[] = + array('modules' => array('gmp', 'php_gmp'), + 'extension' => 'gmp', + 'class' => 'Auth_OpenID_GmpMathWrapper'); + } + + $result[] = array( + 'modules' => array('bcmath', 'php_bcmath'), + 'extension' => 'bcmath', + 'class' => 'Auth_OpenID_BcMathWrapper'); + + return $result; +} + +/** + * Detect which (if any) math library is available + */ +function Auth_OpenID_detectMathLibrary($exts) +{ + $loaded = false; + + foreach ($exts as $extension) { + // See if the extension specified is already loaded. + if ($extension['extension'] && + extension_loaded($extension['extension'])) { + $loaded = true; + } + + // Try to load dynamic modules. + if (!$loaded) { + foreach ($extension['modules'] as $module) { + if (@dl($module . "." . PHP_SHLIB_SUFFIX)) { + $loaded = true; + break; + } + } + } + + // If the load succeeded, supply an instance of + // Auth_OpenID_MathWrapper which wraps the specified + // module's functionality. + if ($loaded) { + return $extension; + } + } + + return false; +} + +/** + * {@link Auth_OpenID_getMathLib} checks for the presence of long + * number extension modules and returns an instance of + * {@link Auth_OpenID_MathWrapper} which exposes the module's + * functionality. + * + * Checks for the existence of an extension module described by the + * result of {@link Auth_OpenID_math_extensions()} and returns an + * instance of a wrapper for that extension module. If no extension + * module is found, an instance of {@link Auth_OpenID_MathWrapper} is + * returned, which wraps the native PHP integer implementation. The + * proper calling convention for this method is $lib =& + * Auth_OpenID_getMathLib(). + * + * This function checks for the existence of specific long number + * implementations in the following order: GMP followed by BCmath. + * + * @return Auth_OpenID_MathWrapper $instance An instance of + * {@link Auth_OpenID_MathWrapper} or one of its subclasses + * + * @package OpenID + */ +function &Auth_OpenID_getMathLib() +{ + // The instance of Auth_OpenID_MathWrapper that we choose to + // supply will be stored here, so that subseqent calls to this + // method will return a reference to the same object. + static $lib = null; + + if (isset($lib)) { + return $lib; + } + + if (Auth_OpenID_noMathSupport()) { + $null = null; + return $null; + } + + // If this method has not been called before, look at + // Auth_OpenID_math_extensions and try to find an extension that + // works. + $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions()); + if ($ext === false) { + $tried = array(); + foreach (Auth_OpenID_math_extensions() as $extinfo) { + $tried[] = $extinfo['extension']; + } + $triedstr = implode(", ", $tried); + + Auth_OpenID_setNoMathSupport(); + + $result = null; + return $result; + } + + // Instantiate a new wrapper + $class = $ext['class']; + $lib = new $class(); + + return $lib; +} + +function Auth_OpenID_setNoMathSupport() +{ + if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) { + define('Auth_OpenID_NO_MATH_SUPPORT', true); + } +} + +function Auth_OpenID_noMathSupport() +{ + return defined('Auth_OpenID_NO_MATH_SUPPORT'); +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Consumer.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Consumer.php new file mode 100644 index 00000000..aecdc410 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Consumer.php @@ -0,0 +1,2232 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Require utility classes and functions for the consumer. + */ +require_once "Auth/OpenID.php"; +require_once "Auth/OpenID/Message.php"; +require_once "Auth/OpenID/HMAC.php"; +require_once "Auth/OpenID/Association.php"; +require_once "Auth/OpenID/CryptUtil.php"; +require_once "Auth/OpenID/DiffieHellman.php"; +require_once "Auth/OpenID/KVForm.php"; +require_once "Auth/OpenID/Nonce.php"; +require_once "Auth/OpenID/Discover.php"; +require_once "Auth/OpenID/URINorm.php"; +require_once "Auth/Yadis/Manager.php"; +require_once "Auth/Yadis/XRI.php"; + +/** + * This is the status code returned when the complete method returns + * successfully. + */ +define('Auth_OpenID_SUCCESS', 'success'); + +/** + * Status to indicate cancellation of OpenID authentication. + */ +define('Auth_OpenID_CANCEL', 'cancel'); + +/** + * This is the status code completeAuth returns when the value it + * received indicated an invalid login. + */ +define('Auth_OpenID_FAILURE', 'failure'); + +/** + * This is the status code completeAuth returns when the + * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the + * identity server sends back a URL to send the user to to complete his + * or her login. + */ +define('Auth_OpenID_SETUP_NEEDED', 'setup needed'); + +/** + * This is the status code beginAuth returns when the page fetched + * from the entered OpenID URL doesn't contain the necessary link tags + * to function as an identity page. + */ +define('Auth_OpenID_PARSE_ERROR', 'parse error'); + +/** + * An OpenID consumer implementation that performs discovery and does + * session management. See the Consumer.php file documentation for + * more information. + * + * @package OpenID + */ +class Auth_OpenID_Consumer { + + /** + * @access private + */ + var $discoverMethod = 'Auth_OpenID_discover'; + + /** + * @access private + */ + var $session_key_prefix = "_openid_consumer_"; + + /** + * @access private + */ + var $_token_suffix = "last_token"; + + /** + * Initialize a Consumer instance. + * + * You should create a new instance of the Consumer object with + * every HTTP request that handles OpenID transactions. + * + * @param Auth_OpenID_OpenIDStore $store This must be an object + * that implements the interface in {@link + * Auth_OpenID_OpenIDStore}. Several concrete implementations are + * provided, to cover most common use cases. For stores backed by + * MySQL, PostgreSQL, or SQLite, see the {@link + * Auth_OpenID_SQLStore} class and its sublcasses. For a + * filesystem-backed store, see the {@link Auth_OpenID_FileStore} + * module. As a last resort, if it isn't possible for the server + * to store state at all, an instance of {@link + * Auth_OpenID_DumbStore} can be used. + * + * @param mixed $session An object which implements the interface + * of the {@link Auth_Yadis_PHPSession} class. Particularly, this + * object is expected to have these methods: get($key), set($key), + * $value), and del($key). This defaults to a session object + * which wraps PHP's native session machinery. You should only + * need to pass something here if you have your own sessioning + * implementation. + * + * @param str $consumer_cls The name of the class to instantiate + * when creating the internal consumer object. This is used for + * testing. + */ + function Auth_OpenID_Consumer(&$store, $session = null, + $consumer_cls = null) + { + if ($session === null) { + $session = new Auth_Yadis_PHPSession(); + } + + $this->session =& $session; + + if ($consumer_cls !== null) { + $this->consumer =& new $consumer_cls($store); + } else { + $this->consumer =& new Auth_OpenID_GenericConsumer($store); + } + + $this->_token_key = $this->session_key_prefix . $this->_token_suffix; + } + + /** + * Used in testing to define the discovery mechanism. + * + * @access private + */ + function getDiscoveryObject(&$session, $openid_url, + $session_key_prefix) + { + return new Auth_Yadis_Discovery($session, $openid_url, + $session_key_prefix); + } + + /** + * Start the OpenID authentication process. See steps 1-2 in the + * overview at the top of this file. + * + * @param string $user_url Identity URL given by the user. This + * method performs a textual transformation of the URL to try and + * make sure it is normalized. For example, a user_url of + * example.com will be normalized to http://example.com/ + * normalizing and resolving any redirects the server might issue. + * + * @param bool $anonymous True if the OpenID request is to be sent + * to the server without any identifier information. Use this + * when you want to transport data but don't want to do OpenID + * authentication with identifiers. + * + * @return Auth_OpenID_AuthRequest $auth_request An object + * containing the discovered information will be returned, with a + * method for building a redirect URL to the server, as described + * in step 3 of the overview. This object may also be used to add + * extension arguments to the request, using its 'addExtensionArg' + * method. + */ + function begin($user_url, $anonymous=false) + { + $openid_url = $user_url; + + $disco = $this->getDiscoveryObject($this->session, + $openid_url, + $this->session_key_prefix); + + // Set the 'stale' attribute of the manager. If discovery + // fails in a fatal way, the stale flag will cause the manager + // to be cleaned up next time discovery is attempted. + + $m = $disco->getManager(); + $loader = new Auth_Yadis_ManagerLoader(); + + if ($m) { + if ($m->stale) { + $disco->destroyManager(); + } else { + $m->stale = true; + $disco->session->set($disco->session_key, + serialize($loader->toSession($m))); + } + } + + $endpoint = $disco->getNextService($this->discoverMethod, + $this->consumer->fetcher); + + // Reset the 'stale' attribute of the manager. + $m =& $disco->getManager(); + if ($m) { + $m->stale = false; + $disco->session->set($disco->session_key, + serialize($loader->toSession($m))); + } + + if ($endpoint === null) { + return null; + } else { + return $this->beginWithoutDiscovery($endpoint, + $anonymous); + } + } + + /** + * Start OpenID verification without doing OpenID server + * discovery. This method is used internally by Consumer.begin + * after discovery is performed, and exists to provide an + * interface for library users needing to perform their own + * discovery. + * + * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service + * endpoint descriptor. + * + * @param bool anonymous Set to true if you want to perform OpenID + * without identifiers. + * + * @return Auth_OpenID_AuthRequest $auth_request An OpenID + * authentication request object. + */ + function &beginWithoutDiscovery($endpoint, $anonymous=false) + { + $loader = new Auth_OpenID_ServiceEndpointLoader(); + $auth_req = $this->consumer->begin($endpoint); + $this->session->set($this->_token_key, + $loader->toSession($auth_req->endpoint)); + if (!$auth_req->setAnonymous($anonymous)) { + return new Auth_OpenID_FailureResponse(null, + "OpenID 1 requests MUST include the identifier " . + "in the request."); + } + return $auth_req; + } + + /** + * Called to interpret the server's response to an OpenID + * request. It is called in step 4 of the flow described in the + * consumer overview. + * + * @param string $current_url The URL used to invoke the application. + * Extract the URL from your application's web + * request framework and specify it here to have it checked + * against the openid.current_url value in the response. If + * the current_url URL check fails, the status of the + * completion will be FAILURE. + * + * @param array $query An array of the query parameters (key => + * value pairs) for this HTTP request. Defaults to null. If + * null, the GET or POST data are automatically gotten from the + * PHP environment. It is only useful to override $query for + * testing. + * + * @return Auth_OpenID_ConsumerResponse $response A instance of an + * Auth_OpenID_ConsumerResponse subclass. The type of response is + * indicated by the status attribute, which will be one of + * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED. + */ + function complete($current_url, $query=null) + { + if ($current_url && !is_string($current_url)) { + // This is ugly, but we need to complain loudly when + // someone uses the API incorrectly. + trigger_error("current_url must be a string; see NEWS file " . + "for upgrading notes.", + E_USER_ERROR); + } + + if ($query === null) { + $query = Auth_OpenID::getQuery(); + } + + $loader = new Auth_OpenID_ServiceEndpointLoader(); + $endpoint_data = $this->session->get($this->_token_key); + $endpoint = + $loader->fromSession($endpoint_data); + + $message = Auth_OpenID_Message::fromPostArgs($query); + $response = $this->consumer->complete($message, $endpoint, + $current_url); + $this->session->del($this->_token_key); + + if (in_array($response->status, array(Auth_OpenID_SUCCESS, + Auth_OpenID_CANCEL))) { + if ($response->identity_url !== null) { + $disco = $this->getDiscoveryObject($this->session, + $response->identity_url, + $this->session_key_prefix); + $disco->cleanup(true); + } + } + + return $response; + } +} + +/** + * A class implementing HMAC/DH-SHA1 consumer sessions. + * + * @package OpenID + */ +class Auth_OpenID_DiffieHellmanSHA1ConsumerSession { + var $session_type = 'DH-SHA1'; + var $hash_func = 'Auth_OpenID_SHA1'; + var $secret_size = 20; + var $allowed_assoc_types = array('HMAC-SHA1'); + + function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null) + { + if ($dh === null) { + $dh = new Auth_OpenID_DiffieHellman(); + } + + $this->dh = $dh; + } + + function getRequest() + { + $math =& Auth_OpenID_getMathLib(); + + $cpub = $math->longToBase64($this->dh->public); + + $args = array('dh_consumer_public' => $cpub); + + if (!$this->dh->usingDefaultValues()) { + $args = array_merge($args, array( + 'dh_modulus' => + $math->longToBase64($this->dh->mod), + 'dh_gen' => + $math->longToBase64($this->dh->gen))); + } + + return $args; + } + + function extractSecret($response) + { + if (!$response->hasKey(Auth_OpenID_OPENID_NS, + 'dh_server_public')) { + return null; + } + + if (!$response->hasKey(Auth_OpenID_OPENID_NS, + 'enc_mac_key')) { + return null; + } + + $math =& Auth_OpenID_getMathLib(); + + $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS, + 'dh_server_public')); + $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS, + 'enc_mac_key')); + + return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func); + } +} + +/** + * A class implementing HMAC/DH-SHA256 consumer sessions. + * + * @package OpenID + */ +class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends + Auth_OpenID_DiffieHellmanSHA1ConsumerSession { + var $session_type = 'DH-SHA256'; + var $hash_func = 'Auth_OpenID_SHA256'; + var $secret_size = 32; + var $allowed_assoc_types = array('HMAC-SHA256'); +} + +/** + * A class implementing plaintext consumer sessions. + * + * @package OpenID + */ +class Auth_OpenID_PlainTextConsumerSession { + var $session_type = 'no-encryption'; + var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256'); + + function getRequest() + { + return array(); + } + + function extractSecret($response) + { + if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) { + return null; + } + + return base64_decode($response->getArg(Auth_OpenID_OPENID_NS, + 'mac_key')); + } +} + +/** + * Returns available session types. + */ +function Auth_OpenID_getAvailableSessionTypes() +{ + $types = array( + 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession', + 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession', + 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession'); + + return $types; +} + +/** + * This class is the interface to the OpenID consumer logic. + * Instances of it maintain no per-request state, so they can be + * reused (or even used by multiple threads concurrently) as needed. + * + * @package OpenID + */ +class Auth_OpenID_GenericConsumer { + /** + * @access private + */ + var $discoverMethod = 'Auth_OpenID_discover'; + + /** + * This consumer's store object. + */ + var $store; + + /** + * @access private + */ + var $_use_assocs; + + /** + * @access private + */ + var $openid1_nonce_query_arg_name = 'janrain_nonce'; + + /** + * Another query parameter that gets added to the return_to for + * OpenID 1; if the user's session state is lost, use this claimed + * identifier to do discovery when verifying the response. + */ + var $openid1_return_to_identifier_name = 'openid1_claimed_id'; + + /** + * This method initializes a new {@link Auth_OpenID_Consumer} + * instance to access the library. + * + * @param Auth_OpenID_OpenIDStore $store This must be an object + * that implements the interface in {@link Auth_OpenID_OpenIDStore}. + * Several concrete implementations are provided, to cover most common use + * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see + * the {@link Auth_OpenID_SQLStore} class and its sublcasses. For a + * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module. + * As a last resort, if it isn't possible for the server to store + * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used. + * + * @param bool $immediate This is an optional boolean value. It + * controls whether the library uses immediate mode, as explained + * in the module description. The default value is False, which + * disables immediate mode. + */ + function Auth_OpenID_GenericConsumer(&$store) + { + $this->store =& $store; + $this->negotiator =& Auth_OpenID_getDefaultNegotiator(); + $this->_use_assocs = ($this->store ? true : false); + + $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + + $this->session_types = Auth_OpenID_getAvailableSessionTypes(); + } + + /** + * Called to begin OpenID authentication using the specified + * {@link Auth_OpenID_ServiceEndpoint}. + * + * @access private + */ + function begin($service_endpoint) + { + $assoc = $this->_getAssociation($service_endpoint); + $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc); + $r->return_to_args[$this->openid1_nonce_query_arg_name] = + Auth_OpenID_mkNonce(); + + if ($r->message->isOpenID1()) { + $r->return_to_args[$this->openid1_return_to_identifier_name] = + $r->endpoint->claimed_id; + } + + return $r; + } + + /** + * Given an {@link Auth_OpenID_Message}, {@link + * Auth_OpenID_ServiceEndpoint} and optional return_to URL, + * complete OpenID authentication. + * + * @access private + */ + function complete($message, $endpoint, $return_to) + { + $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode', + ''); + + $mode_methods = array( + 'cancel' => '_complete_cancel', + 'error' => '_complete_error', + 'setup_needed' => '_complete_setup_needed', + 'id_res' => '_complete_id_res', + ); + + $method = Auth_OpenID::arrayGet($mode_methods, $mode, + '_completeInvalid'); + + return call_user_func_array(array(&$this, $method), + array($message, $endpoint, $return_to)); + } + + /** + * @access private + */ + function _completeInvalid($message, &$endpoint, $unused) + { + $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode', + ''); + + return new Auth_OpenID_FailureResponse($endpoint, + sprintf("Invalid openid.mode '%s'", $mode)); + } + + /** + * @access private + */ + function _complete_cancel($message, &$endpoint, $unused) + { + return new Auth_OpenID_CancelResponse($endpoint); + } + + /** + * @access private + */ + function _complete_error($message, &$endpoint, $unused) + { + $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error'); + $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact'); + $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference'); + + return new Auth_OpenID_FailureResponse($endpoint, $error, + $contact, $reference); + } + + /** + * @access private + */ + function _complete_setup_needed($message, &$endpoint, $unused) + { + if (!$message->isOpenID2()) { + return $this->_completeInvalid($message, $endpoint); + } + + $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS, + 'user_setup_url'); + return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url); + } + + /** + * @access private + */ + function _complete_id_res($message, &$endpoint, $return_to) + { + $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS, + 'user_setup_url'); + + if ($this->_checkSetupNeeded($message)) { + return new Auth_OpenID_SetupNeededResponse( + $endpoint, $user_setup_url); + } else { + return $this->_doIdRes($message, $endpoint, $return_to); + } + } + + /** + * @access private + */ + function _checkSetupNeeded($message) + { + // In OpenID 1, we check to see if this is a cancel from + // immediate mode by the presence of the user_setup_url + // parameter. + if ($message->isOpenID1()) { + $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS, + 'user_setup_url'); + if ($user_setup_url !== null) { + return true; + } + } + + return false; + } + + /** + * @access private + */ + function _doIdRes($message, $endpoint, $return_to) + { + // Checks for presence of appropriate fields (and checks + // signed list fields) + $result = $this->_idResCheckForFields($message); + + if (Auth_OpenID::isFailure($result)) { + return $result; + } + + if (!$this->_checkReturnTo($message, $return_to)) { + return new Auth_OpenID_FailureResponse(null, + sprintf("return_to does not match return URL. Expected %s, got %s", + $return_to, + $message->getArg(Auth_OpenID_OPENID_NS, 'return_to'))); + } + + // Verify discovery information: + $result = $this->_verifyDiscoveryResults($message, $endpoint); + + if (Auth_OpenID::isFailure($result)) { + return $result; + } + + $endpoint = $result; + + $result = $this->_idResCheckSignature($message, + $endpoint->server_url); + + if (Auth_OpenID::isFailure($result)) { + return $result; + } + + $result = $this->_idResCheckNonce($message, $endpoint); + + if (Auth_OpenID::isFailure($result)) { + return $result; + } + + $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed', + Auth_OpenID_NO_DEFAULT); + if (Auth_OpenID::isFailure($signed_list_str)) { + return $signed_list_str; + } + $signed_list = explode(',', $signed_list_str); + + $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid."); + + return new Auth_OpenID_SuccessResponse($endpoint, $message, + $signed_fields); + + } + + /** + * @access private + */ + function _checkReturnTo($message, $return_to) + { + // Check an OpenID message and its openid.return_to value + // against a return_to URL from an application. Return True + // on success, False on failure. + + // Check the openid.return_to args against args in the + // original message. + $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs( + $message->toPostArgs()); + if (Auth_OpenID::isFailure($result)) { + return false; + } + + // Check the return_to base URL against the one in the + // message. + $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS, + 'return_to'); + if (Auth_OpenID::isFailure($return_to)) { + // XXX log me + return false; + } + + $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to)); + $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to)); + + // If port is absent from both, add it so it's equal in the + // check below. + if ((!array_key_exists('port', $return_to_parts)) && + (!array_key_exists('port', $msg_return_to_parts))) { + $return_to_parts['port'] = null; + $msg_return_to_parts['port'] = null; + } + + // If path is absent from both, add it so it's equal in the + // check below. + if ((!array_key_exists('path', $return_to_parts)) && + (!array_key_exists('path', $msg_return_to_parts))) { + $return_to_parts['path'] = null; + $msg_return_to_parts['path'] = null; + } + + // The URL scheme, authority, and path MUST be the same + // between the two URLs. + foreach (array('scheme', 'host', 'port', 'path') as $component) { + // If the url component is absent in either URL, fail. + // There should always be a scheme, host, port, and path. + if (!array_key_exists($component, $return_to_parts)) { + return false; + } + + if (!array_key_exists($component, $msg_return_to_parts)) { + return false; + } + + if (Auth_OpenID::arrayGet($return_to_parts, $component) !== + Auth_OpenID::arrayGet($msg_return_to_parts, $component)) { + return false; + } + } + + return true; + } + + /** + * @access private + */ + function _verifyReturnToArgs($query) + { + // Verify that the arguments in the return_to URL are present in this + // response. + + $message = Auth_OpenID_Message::fromPostArgs($query); + $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to'); + + if (Auth_OpenID::isFailure($return_to)) { + return $return_to; + } + // XXX: this should be checked by _idResCheckForFields + if (!$return_to) { + return new Auth_OpenID_FailureResponse(null, + "Response has no return_to"); + } + + $parsed_url = parse_url($return_to); + + $q = array(); + if (array_key_exists('query', $parsed_url)) { + $rt_query = $parsed_url['query']; + $q = Auth_OpenID::parse_str($rt_query); + } + + foreach ($q as $rt_key => $rt_value) { + if (!array_key_exists($rt_key, $query)) { + return new Auth_OpenID_FailureResponse(null, + sprintf("return_to parameter %s absent from query", $rt_key)); + } else { + $value = $query[$rt_key]; + if ($rt_value != $value) { + return new Auth_OpenID_FailureResponse(null, + sprintf("parameter %s value %s does not match " . + "return_to value %s", $rt_key, + $value, $rt_value)); + } + } + } + + // Make sure all non-OpenID arguments in the response are also + // in the signed return_to. + $bare_args = $message->getArgs(Auth_OpenID_BARE_NS); + foreach ($bare_args as $key => $value) { + if (Auth_OpenID::arrayGet($q, $key) != $value) { + return new Auth_OpenID_FailureResponse(null, + sprintf("Parameter %s = %s not in return_to URL", + $key, $value)); + } + } + + return true; + } + + /** + * @access private + */ + function _idResCheckSignature($message, $server_url) + { + $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, + 'assoc_handle'); + if (Auth_OpenID::isFailure($assoc_handle)) { + return $assoc_handle; + } + + $assoc = $this->store->getAssociation($server_url, $assoc_handle); + + if ($assoc) { + if ($assoc->getExpiresIn() <= 0) { + // XXX: It might be a good idea sometimes to re-start + // the authentication with a new association. Doing it + // automatically opens the possibility for + // denial-of-service by a server that just returns + // expired associations (or really short-lived + // associations) + return new Auth_OpenID_FailureResponse(null, + 'Association with ' . $server_url . ' expired'); + } + + if (!$assoc->checkMessageSignature($message)) { + return new Auth_OpenID_FailureResponse(null, + "Bad signature"); + } + } else { + // It's not an association we know about. Stateless mode + // is our only possible path for recovery. XXX - async + // framework will not want to block on this call to + // _checkAuth. + if (!$this->_checkAuth($message, $server_url)) { + return new Auth_OpenID_FailureResponse(null, + "Server denied check_authentication"); + } + } + + return null; + } + + /** + * @access private + */ + function _verifyDiscoveryResults($message, $endpoint=null) + { + if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) { + return $this->_verifyDiscoveryResultsOpenID2($message, + $endpoint); + } else { + return $this->_verifyDiscoveryResultsOpenID1($message, + $endpoint); + } + } + + /** + * @access private + */ + function _verifyDiscoveryResultsOpenID1($message, $endpoint) + { + $claimed_id = $message->getArg(Auth_OpenID_BARE_NS, + $this->openid1_return_to_identifier_name); + + if (($endpoint === null) && ($claimed_id === null)) { + return new Auth_OpenID_FailureResponse($endpoint, + 'When using OpenID 1, the claimed ID must be supplied, ' . + 'either by passing it through as a return_to parameter ' . + 'or by using a session, and supplied to the GenericConsumer ' . + 'as the argument to complete()'); + } else if (($endpoint !== null) && ($claimed_id === null)) { + $claimed_id = $endpoint->claimed_id; + } + + $to_match = new Auth_OpenID_ServiceEndpoint(); + $to_match->type_uris = array(Auth_OpenID_TYPE_1_1); + $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS, + 'identity'); + + // Restore delegate information from the initiation phase + $to_match->claimed_id = $claimed_id; + + if ($to_match->local_id === null) { + return new Auth_OpenID_FailureResponse($endpoint, + "Missing required field openid.identity"); + } + + $to_match_1_0 = $to_match->copy(); + $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0); + + if ($endpoint !== null) { + $result = $this->_verifyDiscoverySingle($endpoint, $to_match); + + if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) { + $result = $this->_verifyDiscoverySingle($endpoint, + $to_match_1_0); + } + + if (Auth_OpenID::isFailure($result)) { + // oidutil.log("Error attempting to use stored + // discovery information: " + str(e)) + // oidutil.log("Attempting discovery to + // verify endpoint") + } else { + return $endpoint; + } + } + + // Endpoint is either bad (failed verification) or None + return $this->_discoverAndVerify($to_match->claimed_id, + array($to_match, $to_match_1_0)); + } + + /** + * @access private + */ + function _verifyDiscoverySingle($endpoint, $to_match) + { + // Every type URI that's in the to_match endpoint has to be + // present in the discovered endpoint. + foreach ($to_match->type_uris as $type_uri) { + if (!$endpoint->usesExtension($type_uri)) { + return new Auth_OpenID_TypeURIMismatch($endpoint, + "Required type ".$type_uri." not present"); + } + } + + // Fragments do not influence discovery, so we can't compare a + // claimed identifier with a fragment to discovered + // information. + list($defragged_claimed_id, $_) = + Auth_OpenID::urldefrag($to_match->claimed_id); + + if ($defragged_claimed_id != $endpoint->claimed_id) { + return new Auth_OpenID_FailureResponse($endpoint, + sprintf('Claimed ID does not match (different subjects!), ' . + 'Expected %s, got %s', $defragged_claimed_id, + $endpoint->claimed_id)); + } + + if ($to_match->getLocalID() != $endpoint->getLocalID()) { + return new Auth_OpenID_FailureResponse($endpoint, + sprintf('local_id mismatch. Expected %s, got %s', + $to_match->getLocalID(), $endpoint->getLocalID())); + } + + // If the server URL is None, this must be an OpenID 1 + // response, because op_endpoint is a required parameter in + // OpenID 2. In that case, we don't actually care what the + // discovered server_url is, because signature checking or + // check_auth should take care of that check for us. + if ($to_match->server_url === null) { + if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) { + return new Auth_OpenID_FailureResponse($endpoint, + "Preferred namespace mismatch (bug)"); + } + } else if ($to_match->server_url != $endpoint->server_url) { + return new Auth_OpenID_FailureResponse($endpoint, + sprintf('OP Endpoint mismatch. Expected %s, got %s', + $to_match->server_url, $endpoint->server_url)); + } + + return null; + } + + /** + * @access private + */ + function _verifyDiscoveryResultsOpenID2($message, $endpoint) + { + $to_match = new Auth_OpenID_ServiceEndpoint(); + $to_match->type_uris = array(Auth_OpenID_TYPE_2_0); + $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS, + 'claimed_id'); + + $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS, + 'identity'); + + $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS, + 'op_endpoint'); + + if ($to_match->server_url === null) { + return new Auth_OpenID_FailureResponse($endpoint, + "OP Endpoint URL missing"); + } + + // claimed_id and identifier must both be present or both be + // absent + if (($to_match->claimed_id === null) && + ($to_match->local_id !== null)) { + return new Auth_OpenID_FailureResponse($endpoint, + 'openid.identity is present without openid.claimed_id'); + } + + if (($to_match->claimed_id !== null) && + ($to_match->local_id === null)) { + return new Auth_OpenID_FailureResponse($endpoint, + 'openid.claimed_id is present without openid.identity'); + } + + if ($to_match->claimed_id === null) { + // This is a response without identifiers, so there's + // really no checking that we can do, so return an + // endpoint that's for the specified `openid.op_endpoint' + return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL( + $to_match->server_url); + } + + if (!$endpoint) { + // The claimed ID doesn't match, so we have to do + // discovery again. This covers not using sessions, OP + // identifier endpoints and responses that didn't match + // the original request. + // oidutil.log('No pre-discovered information supplied.') + return $this->_discoverAndVerify($to_match->claimed_id, + array($to_match)); + } else { + + // The claimed ID matches, so we use the endpoint that we + // discovered in initiation. This should be the most + // common case. + $result = $this->_verifyDiscoverySingle($endpoint, $to_match); + + if (Auth_OpenID::isFailure($result)) { + $endpoint = $this->_discoverAndVerify($to_match->claimed_id, + array($to_match)); + if (Auth_OpenID::isFailure($endpoint)) { + return $endpoint; + } + } + } + + // The endpoint we return should have the claimed ID from the + // message we just verified, fragment and all. + if ($endpoint->claimed_id != $to_match->claimed_id) { + $endpoint->claimed_id = $to_match->claimed_id; + } + + return $endpoint; + } + + /** + * @access private + */ + function _discoverAndVerify($claimed_id, $to_match_endpoints) + { + // oidutil.log('Performing discovery on %s' % (claimed_id,)) + list($unused, $services) = call_user_func($this->discoverMethod, + $claimed_id, + $this->fetcher); + + if (!$services) { + return new Auth_OpenID_FailureResponse(null, + sprintf("No OpenID information found at %s", + $claimed_id)); + } + + return $this->_verifyDiscoveryServices($claimed_id, $services, + $to_match_endpoints); + } + + /** + * @access private + */ + function _verifyDiscoveryServices($claimed_id, + &$services, &$to_match_endpoints) + { + // Search the services resulting from discovery to find one + // that matches the information from the assertion + + foreach ($services as $endpoint) { + foreach ($to_match_endpoints as $to_match_endpoint) { + $result = $this->_verifyDiscoverySingle($endpoint, + $to_match_endpoint); + + if (!Auth_OpenID::isFailure($result)) { + // It matches, so discover verification has + // succeeded. Return this endpoint. + return $endpoint; + } + } + } + + return new Auth_OpenID_FailureResponse(null, + sprintf('No matching endpoint found after discovering %s', + $claimed_id)); + } + + /** + * Extract the nonce from an OpenID 1 response. Return the nonce + * from the BARE_NS since we independently check the return_to + * arguments are the same as those in the response message. + * + * See the openid1_nonce_query_arg_name class variable + * + * @returns $nonce The nonce as a string or null + * + * @access private + */ + function _idResGetNonceOpenID1($message, $endpoint) + { + return $message->getArg(Auth_OpenID_BARE_NS, + $this->openid1_nonce_query_arg_name); + } + + /** + * @access private + */ + function _idResCheckNonce($message, $endpoint) + { + if ($message->isOpenID1()) { + // This indicates that the nonce was generated by the consumer + $nonce = $this->_idResGetNonceOpenID1($message, $endpoint); + $server_url = ''; + } else { + $nonce = $message->getArg(Auth_OpenID_OPENID2_NS, + 'response_nonce'); + + $server_url = $endpoint->server_url; + } + + if ($nonce === null) { + return new Auth_OpenID_FailureResponse($endpoint, + "Nonce missing from response"); + } + + $parts = Auth_OpenID_splitNonce($nonce); + + if ($parts === null) { + return new Auth_OpenID_FailureResponse($endpoint, + "Malformed nonce in response"); + } + + list($timestamp, $salt) = $parts; + + if (!$this->store->useNonce($server_url, $timestamp, $salt)) { + return new Auth_OpenID_FailureResponse($endpoint, + "Nonce already used or out of range"); + } + + return null; + } + + /** + * @access private + */ + function _idResCheckForFields($message) + { + $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed'); + $basic_sig_fields = array('return_to', 'identity'); + + $require_fields = array( + Auth_OpenID_OPENID2_NS => array_merge($basic_fields, + array('op_endpoint')), + + Auth_OpenID_OPENID1_NS => array_merge($basic_fields, + array('identity')) + ); + + $require_sigs = array( + Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields, + array('response_nonce', + 'claimed_id', + 'assoc_handle')), + Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields, + array('nonce')) + ); + + foreach ($require_fields[$message->getOpenIDNamespace()] as $field) { + if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) { + return new Auth_OpenID_FailureResponse(null, + "Missing required field '".$field."'"); + } + } + + $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, + 'signed', + Auth_OpenID_NO_DEFAULT); + if (Auth_OpenID::isFailure($signed_list_str)) { + return $signed_list_str; + } + $signed_list = explode(',', $signed_list_str); + + foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) { + // Field is present and not in signed list + if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) && + (!in_array($field, $signed_list))) { + return new Auth_OpenID_FailureResponse(null, + "'".$field."' not signed"); + } + } + + return null; + } + + /** + * @access private + */ + function _checkAuth($message, $server_url) + { + $request = $this->_createCheckAuthRequest($message); + if ($request === null) { + return false; + } + + $resp_message = $this->_makeKVPost($request, $server_url); + if (($resp_message === null) || + (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) { + return false; + } + + return $this->_processCheckAuthResponse($resp_message, $server_url); + } + + /** + * @access private + */ + function _createCheckAuthRequest($message) + { + $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed'); + if ($signed) { + foreach (explode(',', $signed) as $k) { + $value = $message->getAliasedArg($k); + if ($value === null) { + return null; + } + } + } + $ca_message = $message->copy(); + $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode', + 'check_authentication'); + return $ca_message; + } + + /** + * @access private + */ + function _processCheckAuthResponse($response, $server_url) + { + $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid', + 'false'); + + $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS, + 'invalidate_handle'); + + if ($invalidate_handle !== null) { + $this->store->removeAssociation($server_url, + $invalidate_handle); + } + + if ($is_valid == 'true') { + return true; + } + + return false; + } + + /** + * Adapt a POST response to a Message. + * + * @param $response Result of a POST to an OpenID endpoint. + * + * @access private + */ + function _httpResponseToMessage($response, $server_url) + { + // Should this function be named Message.fromHTTPResponse instead? + $response_message = Auth_OpenID_Message::fromKVForm($response->body); + + if ($response->status == 400) { + return Auth_OpenID_ServerErrorContainer::fromMessage( + $response_message); + } else if ($response->status != 200 and $response->status != 206) { + return null; + } + + return $response_message; + } + + /** + * @access private + */ + function _makeKVPost($message, $server_url) + { + $body = $message->toURLEncoded(); + $resp = $this->fetcher->post($server_url, $body); + + if ($resp === null) { + return null; + } + + return $this->_httpResponseToMessage($resp, $server_url); + } + + /** + * @access private + */ + function _getAssociation($endpoint) + { + if (!$this->_use_assocs) { + return null; + } + + $assoc = $this->store->getAssociation($endpoint->server_url); + + if (($assoc === null) || + ($assoc->getExpiresIn() <= 0)) { + + $assoc = $this->_negotiateAssociation($endpoint); + + if ($assoc !== null) { + $this->store->storeAssociation($endpoint->server_url, + $assoc); + } + } + + return $assoc; + } + + /** + * Handle ServerErrors resulting from association requests. + * + * @return $result If server replied with an C{unsupported-type} + * error, return a tuple of supported C{association_type}, + * C{session_type}. Otherwise logs the error and returns null. + * + * @access private + */ + function _extractSupportedAssociationType(&$server_error, &$endpoint, + $assoc_type) + { + // Any error message whose code is not 'unsupported-type' + // should be considered a total failure. + if (($server_error->error_code != 'unsupported-type') || + ($server_error->message->isOpenID1())) { + return null; + } + + // The server didn't like the association/session type that we + // sent, and it sent us back a message that might tell us how + // to handle it. + + // Extract the session_type and assoc_type from the error + // message + $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS, + 'assoc_type'); + + $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS, + 'session_type'); + + if (($assoc_type === null) || ($session_type === null)) { + return null; + } else if (!$this->negotiator->isAllowed($assoc_type, + $session_type)) { + return null; + } else { + return array($assoc_type, $session_type); + } + } + + /** + * @access private + */ + function _negotiateAssociation($endpoint) + { + // Get our preferred session/association type from the negotiatior. + list($assoc_type, $session_type) = $this->negotiator->getAllowedType(); + + $assoc = $this->_requestAssociation( + $endpoint, $assoc_type, $session_type); + + if (Auth_OpenID::isFailure($assoc)) { + return null; + } + + if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) { + $why = $assoc; + + $supportedTypes = $this->_extractSupportedAssociationType( + $why, $endpoint, $assoc_type); + + if ($supportedTypes !== null) { + list($assoc_type, $session_type) = $supportedTypes; + + // Attempt to create an association from the assoc_type + // and session_type that the server told us it + // supported. + $assoc = $this->_requestAssociation( + $endpoint, $assoc_type, $session_type); + + if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) { + // Do not keep trying, since it rejected the + // association type that it told us to use. + // oidutil.log('Server %s refused its suggested association + // 'type: session_type=%s, assoc_type=%s' + // % (endpoint.server_url, session_type, + // assoc_type)) + return null; + } else { + return $assoc; + } + } else { + return null; + } + } else { + return $assoc; + } + } + + /** + * @access private + */ + function _requestAssociation($endpoint, $assoc_type, $session_type) + { + list($assoc_session, $args) = $this->_createAssociateRequest( + $endpoint, $assoc_type, $session_type); + + $response_message = $this->_makeKVPost($args, $endpoint->server_url); + + if ($response_message === null) { + // oidutil.log('openid.associate request failed: %s' % (why[0],)) + return null; + } else if (is_a($response_message, + 'Auth_OpenID_ServerErrorContainer')) { + return $response_message; + } + + return $this->_extractAssociation($response_message, $assoc_session); + } + + /** + * @access private + */ + function _extractAssociation(&$assoc_response, &$assoc_session) + { + // Extract the common fields from the response, raising an + // exception if they are not found + $assoc_type = $assoc_response->getArg( + Auth_OpenID_OPENID_NS, 'assoc_type', + Auth_OpenID_NO_DEFAULT); + + if (Auth_OpenID::isFailure($assoc_type)) { + return $assoc_type; + } + + $assoc_handle = $assoc_response->getArg( + Auth_OpenID_OPENID_NS, 'assoc_handle', + Auth_OpenID_NO_DEFAULT); + + if (Auth_OpenID::isFailure($assoc_handle)) { + return $assoc_handle; + } + + // expires_in is a base-10 string. The Python parsing will + // accept literals that have whitespace around them and will + // accept negative values. Neither of these are really in-spec, + // but we think it's OK to accept them. + $expires_in_str = $assoc_response->getArg( + Auth_OpenID_OPENID_NS, 'expires_in', + Auth_OpenID_NO_DEFAULT); + + if (Auth_OpenID::isFailure($expires_in_str)) { + return $expires_in_str; + } + + $expires_in = Auth_OpenID::intval($expires_in_str); + if ($expires_in === false) { + + $err = sprintf("Could not parse expires_in from association ". + "response %s", print_r($assoc_response, true)); + return new Auth_OpenID_FailureResponse(null, $err); + } + + // OpenID 1 has funny association session behaviour. + if ($assoc_response->isOpenID1()) { + $session_type = $this->_getOpenID1SessionType($assoc_response); + } else { + $session_type = $assoc_response->getArg( + Auth_OpenID_OPENID2_NS, 'session_type', + Auth_OpenID_NO_DEFAULT); + + if (Auth_OpenID::isFailure($session_type)) { + return $session_type; + } + } + + // Session type mismatch + if ($assoc_session->session_type != $session_type) { + if ($assoc_response->isOpenID1() && + ($session_type == 'no-encryption')) { + // In OpenID 1, any association request can result in + // a 'no-encryption' association response. Setting + // assoc_session to a new no-encryption session should + // make the rest of this function work properly for + // that case. + $assoc_session = new Auth_OpenID_PlainTextConsumerSession(); + } else { + // Any other mismatch, regardless of protocol version + // results in the failure of the association session + // altogether. + return null; + } + } + + // Make sure assoc_type is valid for session_type + if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) { + return null; + } + + // Delegate to the association session to extract the secret + // from the response, however is appropriate for that session + // type. + $secret = $assoc_session->extractSecret($assoc_response); + + if ($secret === null) { + return null; + } + + return Auth_OpenID_Association::fromExpiresIn( + $expires_in, $assoc_handle, $secret, $assoc_type); + } + + /** + * @access private + */ + function _createAssociateRequest($endpoint, $assoc_type, $session_type) + { + if (array_key_exists($session_type, $this->session_types)) { + $session_type_class = $this->session_types[$session_type]; + + if (is_callable($session_type_class)) { + $assoc_session = $session_type_class(); + } else { + $assoc_session = new $session_type_class(); + } + } else { + return null; + } + + $args = array( + 'mode' => 'associate', + 'assoc_type' => $assoc_type); + + if (!$endpoint->compatibilityMode()) { + $args['ns'] = Auth_OpenID_OPENID2_NS; + } + + // Leave out the session type if we're in compatibility mode + // *and* it's no-encryption. + if ((!$endpoint->compatibilityMode()) || + ($assoc_session->session_type != 'no-encryption')) { + $args['session_type'] = $assoc_session->session_type; + } + + $args = array_merge($args, $assoc_session->getRequest()); + $message = Auth_OpenID_Message::fromOpenIDArgs($args); + return array($assoc_session, $message); + } + + /** + * Given an association response message, extract the OpenID 1.X + * session type. + * + * This function mostly takes care of the 'no-encryption' default + * behavior in OpenID 1. + * + * If the association type is plain-text, this function will + * return 'no-encryption' + * + * @access private + * @return $typ The association type for this message + */ + function _getOpenID1SessionType($assoc_response) + { + // If it's an OpenID 1 message, allow session_type to default + // to None (which signifies "no-encryption") + $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS, + 'session_type'); + + // Handle the differences between no-encryption association + // respones in OpenID 1 and 2: + + // no-encryption is not really a valid session type for OpenID + // 1, but we'll accept it anyway, while issuing a warning. + if ($session_type == 'no-encryption') { + // oidutil.log('WARNING: OpenID server sent "no-encryption"' + // 'for OpenID 1.X') + } else if (($session_type == '') || ($session_type === null)) { + // Missing or empty session type is the way to flag a + // 'no-encryption' response. Change the session type to + // 'no-encryption' so that it can be handled in the same + // way as OpenID 2 'no-encryption' respones. + $session_type = 'no-encryption'; + } + + return $session_type; + } +} + +/** + * This class represents an authentication request from a consumer to + * an OpenID server. + * + * @package OpenID + */ +class Auth_OpenID_AuthRequest { + + /** + * Initialize an authentication request with the specified token, + * association, and endpoint. + * + * Users of this library should not create instances of this + * class. Instances of this class are created by the library when + * needed. + */ + function Auth_OpenID_AuthRequest(&$endpoint, $assoc) + { + $this->assoc = $assoc; + $this->endpoint =& $endpoint; + $this->return_to_args = array(); + $this->message = new Auth_OpenID_Message( + $endpoint->preferredNamespace()); + $this->_anonymous = false; + } + + /** + * Add an extension to this checkid request. + * + * $extension_request: An object that implements the extension + * request interface for adding arguments to an OpenID message. + */ + function addExtension(&$extension_request) + { + $extension_request->toMessage($this->message); + } + + /** + * Add an extension argument to this OpenID authentication + * request. + * + * Use caution when adding arguments, because they will be + * URL-escaped and appended to the redirect URL, which can easily + * get quite long. + * + * @param string $namespace The namespace for the extension. For + * example, the simple registration extension uses the namespace + * 'sreg'. + * + * @param string $key The key within the extension namespace. For + * example, the nickname field in the simple registration + * extension's key is 'nickname'. + * + * @param string $value The value to provide to the server for + * this argument. + */ + function addExtensionArg($namespace, $key, $value) + { + return $this->message->setArg($namespace, $key, $value); + } + + /** + * Set whether this request should be made anonymously. If a + * request is anonymous, the identifier will not be sent in the + * request. This is only useful if you are making another kind of + * request with an extension in this request. + * + * Anonymous requests are not allowed when the request is made + * with OpenID 1. + */ + function setAnonymous($is_anonymous) + { + if ($is_anonymous && $this->message->isOpenID1()) { + return false; + } else { + $this->_anonymous = $is_anonymous; + return true; + } + } + + /** + * Produce a {@link Auth_OpenID_Message} representing this + * request. + * + * @param string $realm The URL (or URL pattern) that identifies + * your web site to the user when she is authorizing it. + * + * @param string $return_to The URL that the OpenID provider will + * send the user back to after attempting to verify her identity. + * + * Not specifying a return_to URL means that the user will not be + * returned to the site issuing the request upon its completion. + * + * @param bool $immediate If true, the OpenID provider is to send + * back a response immediately, useful for behind-the-scenes + * authentication attempts. Otherwise the OpenID provider may + * engage the user before providing a response. This is the + * default case, as the user may need to provide credentials or + * approve the request before a positive response can be sent. + */ + function getMessage($realm, $return_to=null, $immediate=false) + { + if ($return_to) { + $return_to = Auth_OpenID::appendArgs($return_to, + $this->return_to_args); + } else if ($immediate) { + // raise ValueError( + // '"return_to" is mandatory when + //using "checkid_immediate"') + return new Auth_OpenID_FailureResponse(null, + "'return_to' is mandatory when using checkid_immediate"); + } else if ($this->message->isOpenID1()) { + // raise ValueError('"return_to" is + // mandatory for OpenID 1 requests') + return new Auth_OpenID_FailureResponse(null, + "'return_to' is mandatory for OpenID 1 requests"); + } else if ($this->return_to_args) { + // raise ValueError('extra "return_to" arguments + // were specified, but no return_to was specified') + return new Auth_OpenID_FailureResponse(null, + "extra 'return_to' arguments where specified, " . + "but no return_to was specified"); + } + + if ($immediate) { + $mode = 'checkid_immediate'; + } else { + $mode = 'checkid_setup'; + } + + $message = $this->message->copy(); + if ($message->isOpenID1()) { + $realm_key = 'trust_root'; + } else { + $realm_key = 'realm'; + } + + $message->updateArgs(Auth_OpenID_OPENID_NS, + array( + $realm_key => $realm, + 'mode' => $mode, + 'return_to' => $return_to)); + + if (!$this->_anonymous) { + if ($this->endpoint->isOPIdentifier()) { + // This will never happen when we're in compatibility + // mode, as long as isOPIdentifier() returns False + // whenever preferredNamespace() returns OPENID1_NS. + $claimed_id = $request_identity = + Auth_OpenID_IDENTIFIER_SELECT; + } else { + $request_identity = $this->endpoint->getLocalID(); + $claimed_id = $this->endpoint->claimed_id; + } + + // This is true for both OpenID 1 and 2 + $message->setArg(Auth_OpenID_OPENID_NS, 'identity', + $request_identity); + + if ($message->isOpenID2()) { + $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id', + $claimed_id); + } + } + + if ($this->assoc) { + $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle', + $this->assoc->handle); + } + + return $message; + } + + function redirectURL($realm, $return_to = null, + $immediate = false) + { + $message = $this->getMessage($realm, $return_to, $immediate); + + if (Auth_OpenID::isFailure($message)) { + return $message; + } + + return $message->toURL($this->endpoint->server_url); + } + + /** + * Get html for a form to submit this request to the IDP. + * + * form_tag_attrs: An array of attributes to be added to the form + * tag. 'accept-charset' and 'enctype' have defaults that can be + * overridden. If a value is supplied for 'action' or 'method', it + * will be replaced. + */ + function formMarkup($realm, $return_to=null, $immediate=false, + $form_tag_attrs=null) + { + $message = $this->getMessage($realm, $return_to, $immediate); + + if (Auth_OpenID::isFailure($message)) { + return $message; + } + + return $message->toFormMarkup($this->endpoint->server_url, + $form_tag_attrs); + } + + /** + * Get a complete html document that will autosubmit the request + * to the IDP. + * + * Wraps formMarkup. See the documentation for that function. + */ + function htmlMarkup($realm, $return_to=null, $immediate=false, + $form_tag_attrs=null) + { + $form = $this->formMarkup($realm, $return_to, $immediate, + $form_tag_attrs); + + if (Auth_OpenID::isFailure($form)) { + return $form; + } + return Auth_OpenID::autoSubmitHTML($form); + } + + function shouldSendRedirect() + { + return $this->endpoint->compatibilityMode(); + } +} + +/** + * The base class for responses from the Auth_OpenID_Consumer. + * + * @package OpenID + */ +class Auth_OpenID_ConsumerResponse { + var $status = null; + + function setEndpoint($endpoint) + { + $this->endpoint = $endpoint; + if ($endpoint === null) { + $this->identity_url = null; + } else { + $this->identity_url = $endpoint->claimed_id; + } + } + + /** + * Return the display identifier for this response. + * + * The display identifier is related to the Claimed Identifier, but the + * two are not always identical. The display identifier is something the + * user should recognize as what they entered, whereas the response's + * claimed identifier (in the identity_url attribute) may have extra + * information for better persistence. + * + * URLs will be stripped of their fragments for display. XRIs will + * display the human-readable identifier (i-name) instead of the + * persistent identifier (i-number). + * + * Use the display identifier in your user interface. Use + * identity_url for querying your database or authorization server. + * + */ + function getDisplayIdentifier() + { + if ($this->endpoint !== null) { + return $this->endpoint->getDisplayIdentifier(); + } + return null; + } +} + +/** + * A response with a status of Auth_OpenID_SUCCESS. Indicates that + * this request is a successful acknowledgement from the OpenID server + * that the supplied URL is, indeed controlled by the requesting + * agent. This has three relevant attributes: + * + * claimed_id - The identity URL that has been authenticated + * + * signed_args - The arguments in the server's response that were + * signed and verified. + * + * status - Auth_OpenID_SUCCESS. + * + * @package OpenID + */ +class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse { + var $status = Auth_OpenID_SUCCESS; + + /** + * @access private + */ + function Auth_OpenID_SuccessResponse($endpoint, $message, $signed_args=null) + { + $this->endpoint = $endpoint; + $this->identity_url = $endpoint->claimed_id; + $this->signed_args = $signed_args; + $this->message = $message; + + if ($this->signed_args === null) { + $this->signed_args = array(); + } + } + + /** + * Extract signed extension data from the server's response. + * + * @param string $prefix The extension namespace from which to + * extract the extension data. + */ + function extensionResponse($namespace_uri, $require_signed) + { + if ($require_signed) { + return $this->getSignedNS($namespace_uri); + } else { + return $this->message->getArgs($namespace_uri); + } + } + + function isOpenID1() + { + return $this->message->isOpenID1(); + } + + function isSigned($ns_uri, $ns_key) + { + // Return whether a particular key is signed, regardless of + // its namespace alias + return in_array($this->message->getKey($ns_uri, $ns_key), + $this->signed_args); + } + + function getSigned($ns_uri, $ns_key, $default = null) + { + // Return the specified signed field if available, otherwise + // return default + if ($this->isSigned($ns_uri, $ns_key)) { + return $this->message->getArg($ns_uri, $ns_key, $default); + } else { + return $default; + } + } + + function getSignedNS($ns_uri) + { + $args = array(); + + $msg_args = $this->message->getArgs($ns_uri); + if (Auth_OpenID::isFailure($msg_args)) { + return null; + } + + foreach ($msg_args as $key => $value) { + if (!$this->isSigned($ns_uri, $key)) { + return null; + } + } + + return $msg_args; + } + + /** + * Get the openid.return_to argument from this response. + * + * This is useful for verifying that this request was initiated by + * this consumer. + * + * @return string $return_to The return_to URL supplied to the + * server on the initial request, or null if the response did not + * contain an 'openid.return_to' argument. + */ + function getReturnTo() + { + return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to'); + } +} + +/** + * A response with a status of Auth_OpenID_FAILURE. Indicates that the + * OpenID protocol has failed. This could be locally or remotely + * triggered. This has three relevant attributes: + * + * claimed_id - The identity URL for which authentication was + * attempted, if it can be determined. Otherwise, null. + * + * message - A message indicating why the request failed, if one is + * supplied. Otherwise, null. + * + * status - Auth_OpenID_FAILURE. + * + * @package OpenID + */ +class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse { + var $status = Auth_OpenID_FAILURE; + + function Auth_OpenID_FailureResponse($endpoint, $message = null, + $contact = null, $reference = null) + { + $this->setEndpoint($endpoint); + $this->message = $message; + $this->contact = $contact; + $this->reference = $reference; + } +} + +/** + * A specific, internal failure used to detect type URI mismatch. + * + * @package OpenID + */ +class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse { +} + +/** + * Exception that is raised when the server returns a 400 response + * code to a direct request. + * + * @package OpenID + */ +class Auth_OpenID_ServerErrorContainer { + function Auth_OpenID_ServerErrorContainer($error_text, + $error_code, + $message) + { + $this->error_text = $error_text; + $this->error_code = $error_code; + $this->message = $message; + } + + /** + * @access private + */ + function fromMessage($message) + { + $error_text = $message->getArg( + Auth_OpenID_OPENID_NS, 'error', ''); + $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code'); + return new Auth_OpenID_ServerErrorContainer($error_text, + $error_code, + $message); + } +} + +/** + * A response with a status of Auth_OpenID_CANCEL. Indicates that the + * user cancelled the OpenID authentication request. This has two + * relevant attributes: + * + * claimed_id - The identity URL for which authentication was + * attempted, if it can be determined. Otherwise, null. + * + * status - Auth_OpenID_SUCCESS. + * + * @package OpenID + */ +class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse { + var $status = Auth_OpenID_CANCEL; + + function Auth_OpenID_CancelResponse($endpoint) + { + $this->setEndpoint($endpoint); + } +} + +/** + * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates + * that the request was in immediate mode, and the server is unable to + * authenticate the user without further interaction. + * + * claimed_id - The identity URL for which authentication was + * attempted. + * + * setup_url - A URL that can be used to send the user to the server + * to set up for authentication. The user should be redirected in to + * the setup_url, either in the current window or in a new browser + * window. Null in OpenID 2. + * + * status - Auth_OpenID_SETUP_NEEDED. + * + * @package OpenID + */ +class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse { + var $status = Auth_OpenID_SETUP_NEEDED; + + function Auth_OpenID_SetupNeededResponse($endpoint, + $setup_url = null) + { + $this->setEndpoint($endpoint); + $this->setup_url = $setup_url; + } +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/CryptUtil.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/CryptUtil.php new file mode 100644 index 00000000..d75af59b --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/CryptUtil.php @@ -0,0 +1,112 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +if (!defined('Auth_OpenID_RAND_SOURCE')) { + /** + * The filename for a source of random bytes. Define this yourself + * if you have a different source of randomness. + */ + define('Auth_OpenID_RAND_SOURCE', '/dev/urandom'); +} + +class Auth_OpenID_CryptUtil { + /** + * Get the specified number of random bytes. + * + * Attempts to use a cryptographically secure (not predictable) + * source of randomness if available. If there is no high-entropy + * randomness source available, it will fail. As a last resort, + * for non-critical systems, define + * Auth_OpenID_RAND_SOURCE as null, and + * the code will fall back on a pseudo-random number generator. + * + * @param int $num_bytes The length of the return value + * @return string $bytes random bytes + */ + function getBytes($num_bytes) + { + static $f = null; + $bytes = ''; + if ($f === null) { + if (Auth_OpenID_RAND_SOURCE === null) { + $f = false; + } else { + $f = @fopen(Auth_OpenID_RAND_SOURCE, "r"); + if ($f === false) { + $msg = 'Define Auth_OpenID_RAND_SOURCE as null to ' . + ' continue with an insecure random number generator.'; + trigger_error($msg, E_USER_ERROR); + } + } + } + if ($f === false) { + // pseudorandom used + $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; + } + + /** + * Produce a string of length random bytes, chosen from chrs. If + * $chrs is null, the resulting string may contain any characters. + * + * @param integer $length The length of the resulting + * randomly-generated string + * @param string $chrs A string of characters from which to choose + * to build the new string + * @return string $result A string of randomly-chosen characters + * from $chrs + */ + function randomString($length, $population = null) + { + if ($population === null) { + return Auth_OpenID_CryptUtil::getBytes($length); + } + + $popsize = strlen($population); + + if ($popsize > 256) { + $msg = 'More than 256 characters supplied to ' . __FUNCTION__; + trigger_error($msg, E_USER_ERROR); + } + + $duplicate = 256 % $popsize; + + $str = ""; + for ($i = 0; $i < $length; $i++) { + do { + $n = ord(Auth_OpenID_CryptUtil::getBytes(1)); + } while ($n < $duplicate); + + $n %= $popsize; + $str .= $population[$n]; + } + + return $str; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/DatabaseConnection.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DatabaseConnection.php new file mode 100644 index 00000000..0079fb61 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DatabaseConnection.php @@ -0,0 +1,134 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * An empty base class intended to emulate PEAR connection + * functionality in applications that supply their own database + * abstraction mechanisms. See {@link Auth_OpenID_SQLStore} for more + * information. You should subclass this class if you need to create + * an SQL store that needs to access its database using an + * application's database abstraction layer instead of a PEAR database + * connection. Any subclass of Auth_OpenID_DatabaseConnection MUST + * adhere to the interface specified here. + * + * @package OpenID + */ +class Auth_OpenID_DatabaseConnection { + /** + * Sets auto-commit mode on this database connection. + * + * @param bool $mode True if auto-commit is to be used; false if + * not. + */ + function autoCommit($mode) + { + } + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql An SQL string with placeholders. The + * placeholders are assumed to be specific to the database engine + * for this connection. + * + * @param array $params An array of parameters to insert into the + * SQL string using this connection's escaping mechanism. + * + * @return mixed $result The result of calling this connection's + * internal query function. The type of result depends on the + * underlying database engine. This method is usually used when + * the result of a query is not important, like a DDL query. + */ + function query($sql, $params = array()) + { + } + + /** + * Starts a transaction on this connection, if supported. + */ + function begin() + { + } + + /** + * Commits a transaction on this connection, if supported. + */ + function commit() + { + } + + /** + * Performs a rollback on this connection, if supported. + */ + function rollback() + { + } + + /** + * Run an SQL query and return the first column of the first row + * of the result set, if any. + * + * @param string $sql An SQL string with placeholders. The + * placeholders are assumed to be specific to the database engine + * for this connection. + * + * @param array $params An array of parameters to insert into the + * SQL string using this connection's escaping mechanism. + * + * @return mixed $result The value of the first column of the + * first row of the result set. False if no such result was + * found. + */ + function getOne($sql, $params = array()) + { + } + + /** + * Run an SQL query and return the first row of the result set, if + * any. + * + * @param string $sql An SQL string with placeholders. The + * placeholders are assumed to be specific to the database engine + * for this connection. + * + * @param array $params An array of parameters to insert into the + * SQL string using this connection's escaping mechanism. + * + * @return array $result The first row of the result set, if any, + * keyed on column name. False if no such result was found. + */ + function getRow($sql, $params = array()) + { + } + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql An SQL string with placeholders. The + * placeholders are assumed to be specific to the database engine + * for this connection. + * + * @param array $params An array of parameters to insert into the + * SQL string using this connection's escaping mechanism. + * + * @return array $result An array of arrays representing the + * result of the query; each array is keyed on column name. + */ + function getAll($sql, $params = array()) + { + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/DiffieHellman.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DiffieHellman.php new file mode 100644 index 00000000..d1a87262 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DiffieHellman.php @@ -0,0 +1,116 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +require_once 'Auth/OpenID.php'; +require_once 'Auth/OpenID/BigMath.php'; + +function Auth_OpenID_getDefaultMod() +{ + return '155172898181473697471232257763715539915724801'. + '966915404479707795314057629378541917580651227423'. + '698188993727816152646631438561595825688188889951'. + '272158842675419950341258706556549803580104870537'. + '681476726513255747040765857479291291572334510643'. + '245094715007229621094194349783925984760375594985'. + '848253359305585439638443'; +} + +function Auth_OpenID_getDefaultGen() +{ + return '2'; +} + +/** + * The Diffie-Hellman key exchange class. This class relies on + * {@link Auth_OpenID_MathLibrary} to perform large number operations. + * + * @access private + * @package OpenID + */ +class Auth_OpenID_DiffieHellman { + + var $mod; + var $gen; + var $private; + var $lib = null; + + function Auth_OpenID_DiffieHellman($mod = null, $gen = null, + $private = null, $lib = null) + { + if ($lib === null) { + $this->lib =& Auth_OpenID_getMathLib(); + } else { + $this->lib =& $lib; + } + + if ($mod === null) { + $this->mod = $this->lib->init(Auth_OpenID_getDefaultMod()); + } else { + $this->mod = $mod; + } + + if ($gen === null) { + $this->gen = $this->lib->init(Auth_OpenID_getDefaultGen()); + } else { + $this->gen = $gen; + } + + if ($private === null) { + $r = $this->lib->rand($this->mod); + $this->private = $this->lib->add($r, 1); + } else { + $this->private = $private; + } + + $this->public = $this->lib->powmod($this->gen, $this->private, + $this->mod); + } + + function getSharedSecret($composite) + { + return $this->lib->powmod($composite, $this->private, $this->mod); + } + + function getPublicKey() + { + return $this->public; + } + + function usingDefaultValues() + { + return ($this->mod == Auth_OpenID_getDefaultMod() && + $this->gen == Auth_OpenID_getDefaultGen()); + } + + function xorSecret($composite, $secret, $hash_func) + { + $dh_shared = $this->getSharedSecret($composite); + $dh_shared_str = $this->lib->longToBinary($dh_shared); + $hash_dh_shared = $hash_func($dh_shared_str); + + $xsecret = ""; + for ($i = 0; $i < Auth_OpenID::bytes($secret); $i++) { + $xsecret .= chr(ord($secret[$i]) ^ ord($hash_dh_shared[$i])); + } + + return $xsecret; + } +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Discover.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Discover.php new file mode 100644 index 00000000..7d224dcb --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Discover.php @@ -0,0 +1,551 @@ +claimed_id = null; + $this->server_url = null; + $this->type_uris = array(); + $this->local_id = null; + $this->canonicalID = null; + $this->used_yadis = false; // whether this came from an XRDS + $this->display_identifier = null; + } + + function getDisplayIdentifier() + { + if ($this->display_identifier) { + return $this->display_identifier; + } + if (! $this->claimed_id) { + return $this->claimed_id; + } + $parsed = parse_url($this->claimed_id); + $scheme = $parsed['scheme']; + $host = $parsed['host']; + $path = $parsed['path']; + if (array_key_exists('query', $parsed)) { + $query = $parsed['query']; + $no_frag = "$scheme://$host$path?$query"; + } else { + $no_frag = "$scheme://$host$path"; + } + return $no_frag; + } + + function usesExtension($extension_uri) + { + return in_array($extension_uri, $this->type_uris); + } + + function preferredNamespace() + { + if (in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris) || + in_array(Auth_OpenID_TYPE_2_0, $this->type_uris)) { + return Auth_OpenID_OPENID2_NS; + } else { + return Auth_OpenID_OPENID1_NS; + } + } + + /* + * Query this endpoint to see if it has any of the given type + * URIs. This is useful for implementing other endpoint classes + * that e.g. need to check for the presence of multiple versions + * of a single protocol. + * + * @param $type_uris The URIs that you wish to check + * + * @return all types that are in both in type_uris and + * $this->type_uris + */ + function matchTypes($type_uris) + { + $result = array(); + foreach ($type_uris as $test_uri) { + if ($this->supportsType($test_uri)) { + $result[] = $test_uri; + } + } + + return $result; + } + + function supportsType($type_uri) + { + // Does this endpoint support this type? + return ((in_array($type_uri, $this->type_uris)) || + (($type_uri == Auth_OpenID_TYPE_2_0) && + $this->isOPIdentifier())); + } + + function compatibilityMode() + { + return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS; + } + + function isOPIdentifier() + { + return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris); + } + + function fromOPEndpointURL($op_endpoint_url) + { + // Construct an OP-Identifier OpenIDServiceEndpoint object for + // a given OP Endpoint URL + $obj = new Auth_OpenID_ServiceEndpoint(); + $obj->server_url = $op_endpoint_url; + $obj->type_uris = array(Auth_OpenID_TYPE_2_0_IDP); + return $obj; + } + + function parseService($yadis_url, $uri, $type_uris, $service_element) + { + // Set the state of this object based on the contents of the + // service element. Return true if successful, false if not + // (if findOPLocalIdentifier returns false). + $this->type_uris = $type_uris; + $this->server_url = $uri; + $this->used_yadis = true; + + if (!$this->isOPIdentifier()) { + $this->claimed_id = $yadis_url; + $this->local_id = Auth_OpenID_findOPLocalIdentifier( + $service_element, + $this->type_uris); + if ($this->local_id === false) { + return false; + } + } + + return true; + } + + function getLocalID() + { + // Return the identifier that should be sent as the + // openid.identity_url parameter to the server. + if ($this->local_id === null && $this->canonicalID === null) { + return $this->claimed_id; + } else { + if ($this->local_id) { + return $this->local_id; + } else { + return $this->canonicalID; + } + } + } + + /* + * Parse the given document as XRDS looking for OpenID services. + * + * @return array of Auth_OpenID_ServiceEndpoint or null if the + * document cannot be parsed. + */ + function fromXRDS($uri, $xrds_text) + { + $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text); + + if ($xrds) { + $yadis_services = + $xrds->services(array('filter_MatchesAnyOpenIDType')); + return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services); + } + + return null; + } + + /* + * Create endpoints from a DiscoveryResult. + * + * @param discoveryResult Auth_Yadis_DiscoveryResult + * @return array of Auth_OpenID_ServiceEndpoint or null if + * endpoints cannot be created. + */ + function fromDiscoveryResult($discoveryResult) + { + if ($discoveryResult->isXRDS()) { + return Auth_OpenID_ServiceEndpoint::fromXRDS( + $discoveryResult->normalized_uri, + $discoveryResult->response_text); + } else { + return Auth_OpenID_ServiceEndpoint::fromHTML( + $discoveryResult->normalized_uri, + $discoveryResult->response_text); + } + } + + function fromHTML($uri, $html) + { + $discovery_types = array( + array(Auth_OpenID_TYPE_2_0, + 'openid2.provider', 'openid2.local_id'), + array(Auth_OpenID_TYPE_1_1, + 'openid.server', 'openid.delegate') + ); + + $services = array(); + + foreach ($discovery_types as $triple) { + list($type_uri, $server_rel, $delegate_rel) = $triple; + + $urls = Auth_OpenID_legacy_discover($html, $server_rel, + $delegate_rel); + + if ($urls === false) { + continue; + } + + list($delegate_url, $server_url) = $urls; + + $service = new Auth_OpenID_ServiceEndpoint(); + $service->claimed_id = $uri; + $service->local_id = $delegate_url; + $service->server_url = $server_url; + $service->type_uris = array($type_uri); + + $services[] = $service; + } + + return $services; + } + + function copy() + { + $x = new Auth_OpenID_ServiceEndpoint(); + + $x->claimed_id = $this->claimed_id; + $x->server_url = $this->server_url; + $x->type_uris = $this->type_uris; + $x->local_id = $this->local_id; + $x->canonicalID = $this->canonicalID; + $x->used_yadis = $this->used_yadis; + + return $x; + } +} + +function Auth_OpenID_findOPLocalIdentifier($service, $type_uris) +{ + // Extract a openid:Delegate value from a Yadis Service element. + // If no delegate is found, returns null. Returns false on + // discovery failure (when multiple delegate/localID tags have + // different values). + + $service->parser->registerNamespace('openid', + Auth_OpenID_XMLNS_1_0); + + $service->parser->registerNamespace('xrd', + Auth_Yadis_XMLNS_XRD_2_0); + + $parser =& $service->parser; + + $permitted_tags = array(); + + if (in_array(Auth_OpenID_TYPE_1_1, $type_uris) || + in_array(Auth_OpenID_TYPE_1_0, $type_uris)) { + $permitted_tags[] = 'openid:Delegate'; + } + + if (in_array(Auth_OpenID_TYPE_2_0, $type_uris)) { + $permitted_tags[] = 'xrd:LocalID'; + } + + $local_id = null; + + foreach ($permitted_tags as $tag_name) { + $tags = $service->getElements($tag_name); + + foreach ($tags as $tag) { + $content = $parser->content($tag); + + if ($local_id === null) { + $local_id = $content; + } else if ($local_id != $content) { + return false; + } + } + } + + return $local_id; +} + +function filter_MatchesAnyOpenIDType(&$service) +{ + $uris = $service->getTypes(); + + foreach ($uris as $uri) { + if (in_array($uri, Auth_OpenID_getOpenIDTypeURIs())) { + return true; + } + } + + return false; +} + +function Auth_OpenID_bestMatchingService($service, $preferred_types) +{ + // Return the index of the first matching type, or something + // higher if no type matches. + // + // This provides an ordering in which service elements that + // contain a type that comes earlier in the preferred types list + // come before service elements that come later. If a service + // element has more than one type, the most preferred one wins. + + foreach ($preferred_types as $index => $typ) { + if (in_array($typ, $service->type_uris)) { + return $index; + } + } + + return count($preferred_types); +} + +function Auth_OpenID_arrangeByType($service_list, $preferred_types) +{ + // Rearrange service_list in a new list so services are ordered by + // types listed in preferred_types. Return the new list. + + // Build a list with the service elements in tuples whose + // comparison will prefer the one with the best matching service + $prio_services = array(); + foreach ($service_list as $index => $service) { + $prio_services[] = array(Auth_OpenID_bestMatchingService($service, + $preferred_types), + $index, $service); + } + + sort($prio_services); + + // Now that the services are sorted by priority, remove the sort + // keys from the list. + foreach ($prio_services as $index => $s) { + $prio_services[$index] = $prio_services[$index][2]; + } + + return $prio_services; +} + +// Extract OP Identifier services. If none found, return the rest, +// sorted with most preferred first according to +// OpenIDServiceEndpoint.openid_type_uris. +// +// openid_services is a list of OpenIDServiceEndpoint objects. +// +// Returns a list of OpenIDServiceEndpoint objects.""" +function Auth_OpenID_getOPOrUserServices($openid_services) +{ + $op_services = Auth_OpenID_arrangeByType($openid_services, + array(Auth_OpenID_TYPE_2_0_IDP)); + + $openid_services = Auth_OpenID_arrangeByType($openid_services, + Auth_OpenID_getOpenIDTypeURIs()); + + if ($op_services) { + return $op_services; + } else { + return $openid_services; + } +} + +function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services) +{ + $s = array(); + + if (!$yadis_services) { + return $s; + } + + foreach ($yadis_services as $service) { + $type_uris = $service->getTypes(); + $uris = $service->getURIs(); + + // If any Type URIs match and there is an endpoint URI + // specified, then this is an OpenID endpoint + if ($type_uris && + $uris) { + foreach ($uris as $service_uri) { + $openid_endpoint = new Auth_OpenID_ServiceEndpoint(); + if ($openid_endpoint->parseService($uri, + $service_uri, + $type_uris, + $service)) { + $s[] = $openid_endpoint; + } + } + } + } + + return $s; +} + +function Auth_OpenID_discoverWithYadis($uri, &$fetcher, + $endpoint_filter='Auth_OpenID_getOPOrUserServices', + $discover_function=null) +{ + // Discover OpenID services for a URI. Tries Yadis and falls back + // on old-style discovery if Yadis fails. + + // Might raise a yadis.discover.DiscoveryFailure if no document + // came back for that URI at all. I don't think falling back to + // OpenID 1.0 discovery on the same URL will help, so don't bother + // to catch it. + if ($discover_function === null) { + $discover_function = array('Auth_Yadis_Yadis', 'discover'); + } + + $openid_services = array(); + + $response = call_user_func_array($discover_function, + array($uri, &$fetcher)); + + $yadis_url = $response->normalized_uri; + $yadis_services = array(); + + if ($response->isFailure()) { + return array($uri, array()); + } + + $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS( + $yadis_url, + $response->response_text); + + if (!$openid_services) { + if ($response->isXRDS()) { + return Auth_OpenID_discoverWithoutYadis($uri, + $fetcher); + } + + // Try to parse the response as HTML to get OpenID 1.0/1.1 + // + $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML( + $yadis_url, + $response->response_text); + } + + $openid_services = call_user_func_array($endpoint_filter, + array(&$openid_services)); + + return array($yadis_url, $openid_services); +} + +function Auth_OpenID_discoverURI($uri, &$fetcher) +{ + $uri = Auth_OpenID::normalizeUrl($uri); + return Auth_OpenID_discoverWithYadis($uri, $fetcher); +} + +function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher) +{ + $http_resp = @$fetcher->get($uri); + + if ($http_resp->status != 200 and $http_resp->status != 206) { + return array($uri, array()); + } + + $identity_url = $http_resp->final_url; + + // Try to parse the response as HTML to get OpenID 1.0/1.1 + $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML( + $identity_url, + $http_resp->body); + + return array($identity_url, $openid_services); +} + +function Auth_OpenID_discoverXRI($iname, &$fetcher) +{ + $resolver = new Auth_Yadis_ProxyResolver($fetcher); + list($canonicalID, $yadis_services) = + $resolver->query($iname, + Auth_OpenID_getOpenIDTypeURIs(), + array('filter_MatchesAnyOpenIDType')); + + $openid_services = Auth_OpenID_makeOpenIDEndpoints($iname, + $yadis_services); + + $openid_services = Auth_OpenID_getOPOrUserServices($openid_services); + + for ($i = 0; $i < count($openid_services); $i++) { + $openid_services[$i]->canonicalID = $canonicalID; + $openid_services[$i]->claimed_id = $canonicalID; + $openid_services[$i]->display_identifier = $iname; + } + + // FIXME: returned xri should probably be in some normal form + return array($iname, $openid_services); +} + +function Auth_OpenID_discover($uri, &$fetcher) +{ + // If the fetcher (i.e., PHP) doesn't support SSL, we can't do + // discovery on an HTTPS URL. + if ($fetcher->isHTTPS($uri) && !$fetcher->supportsSSL()) { + return array($uri, array()); + } + + if (Auth_Yadis_identifierScheme($uri) == 'XRI') { + $result = Auth_OpenID_discoverXRI($uri, $fetcher); + } else { + $result = Auth_OpenID_discoverURI($uri, $fetcher); + } + + // If the fetcher doesn't support SSL, we can't interact with + // HTTPS server URLs; remove those endpoints from the list. + if (!$fetcher->supportsSSL()) { + $http_endpoints = array(); + list($new_uri, $endpoints) = $result; + + foreach ($endpoints as $e) { + if (!$fetcher->isHTTPS($e->server_url)) { + $http_endpoints[] = $e; + } + } + + $result = array($new_uri, $http_endpoints); + } + + return $result; +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/DumbStore.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DumbStore.php new file mode 100644 index 00000000..6b59c0cb --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/DumbStore.php @@ -0,0 +1,103 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Import the interface for creating a new store class. + */ +require_once 'Auth/OpenID/Interface.php'; +require_once 'Auth/OpenID/HMAC.php'; + +/** + * This is a store for use in the worst case, when you have no way of + * saving state on the consumer site. Using this store makes the + * consumer vulnerable to replay attacks, as it's unable to use + * nonces. Avoid using this store if it is at all possible. + * + * Most of the methods of this class are implementation details. + * Users of this class need to worry only about the constructor. + * + * @package OpenID + */ +class Auth_OpenID_DumbStore extends Auth_OpenID_OpenIDStore { + + /** + * Creates a new {@link Auth_OpenID_DumbStore} instance. For the security + * of the tokens generated by the library, this class attempts to + * at least have a secure implementation of getAuthKey. + * + * When you create an instance of this class, pass in a secret + * phrase. The phrase is hashed with sha1 to make it the correct + * length and form for an auth key. That allows you to use a long + * string as the secret phrase, which means you can make it very + * difficult to guess. + * + * Each {@link Auth_OpenID_DumbStore} instance that is created for use by + * your consumer site needs to use the same $secret_phrase. + * + * @param string secret_phrase The phrase used to create the auth + * key returned by getAuthKey + */ + function Auth_OpenID_DumbStore($secret_phrase) + { + $this->auth_key = Auth_OpenID_SHA1($secret_phrase); + } + + /** + * This implementation does nothing. + */ + function storeAssociation($server_url, $association) + { + } + + /** + * This implementation always returns null. + */ + function getAssociation($server_url, $handle = null) + { + return null; + } + + /** + * This implementation always returns false. + */ + function removeAssociation($server_url, $handle) + { + return false; + } + + /** + * In a system truly limited to dumb mode, nonces must all be + * accepted. This therefore always returns true, which makes + * replay attacks feasible. + */ + function useNonce($server_url, $timestamp, $salt) + { + return true; + } + + /** + * This method returns the auth key generated by the constructor. + */ + function getAuthKey() + { + return $this->auth_key; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Extension.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Extension.php new file mode 100644 index 00000000..1b8fca4e --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Extension.php @@ -0,0 +1,65 @@ +isOpenID1(); + $added = $message->namespaces->addAlias($this->ns_uri, + $this->ns_alias, + $implicit); + + if ($added === null) { + if ($message->namespaces->getAlias($this->ns_uri) != + $this->ns_alias) { + return null; + } + } + + $message->updateArgs($this->ns_uri, + $this->getExtensionArgs()); + return $message; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/FileStore.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/FileStore.php new file mode 100644 index 00000000..434d7a78 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/FileStore.php @@ -0,0 +1,621 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Require base class for creating a new interface. + */ +require_once 'Auth/OpenID.php'; +require_once 'Auth/OpenID/Interface.php'; +require_once 'Auth/OpenID/HMAC.php'; +require_once 'Auth/OpenID/Nonce.php'; + +/** + * This is a filesystem-based store for OpenID associations and + * nonces. This store should be safe for use in concurrent systems on + * both windows and unix (excluding NFS filesystems). There are a + * couple race conditions in the system, but those failure cases have + * been set up in such a way that the worst-case behavior is someone + * having to try to log in a second time. + * + * Most of the methods of this class are implementation details. + * People wishing to just use this store need only pay attention to + * the constructor. + * + * @package OpenID + */ +class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore { + + /** + * Initializes a new {@link Auth_OpenID_FileStore}. This + * initializes the nonce and association directories, which are + * subdirectories of the directory passed in. + * + * @param string $directory This is the directory to put the store + * directories in. + */ + function Auth_OpenID_FileStore($directory) + { + if (!Auth_OpenID::ensureDir($directory)) { + trigger_error('Not a directory and failed to create: ' + . $directory, E_USER_ERROR); + } + $directory = realpath($directory); + + $this->directory = $directory; + $this->active = true; + + $this->nonce_dir = $directory . DIRECTORY_SEPARATOR . 'nonces'; + + $this->association_dir = $directory . DIRECTORY_SEPARATOR . + 'associations'; + + // Temp dir must be on the same filesystem as the assciations + // $directory. + $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp'; + + $this->max_nonce_age = 6 * 60 * 60; // Six hours, in seconds + + if (!$this->_setup()) { + trigger_error('Failed to initialize OpenID file store in ' . + $directory, E_USER_ERROR); + } + } + + function destroy() + { + Auth_OpenID_FileStore::_rmtree($this->directory); + $this->active = false; + } + + /** + * Make sure that the directories in which we store our data + * exist. + * + * @access private + */ + function _setup() + { + return (Auth_OpenID::ensureDir($this->nonce_dir) && + Auth_OpenID::ensureDir($this->association_dir) && + Auth_OpenID::ensureDir($this->temp_dir)); + } + + /** + * Create a temporary file on the same filesystem as + * $this->association_dir. + * + * The temporary directory should not be cleaned if there are any + * processes using the store. If there is no active process using + * the store, it is safe to remove all of the files in the + * temporary directory. + * + * @return array ($fd, $filename) + * @access private + */ + function _mktemp() + { + $name = Auth_OpenID_FileStore::_mkstemp($dir = $this->temp_dir); + $file_obj = @fopen($name, 'wb'); + if ($file_obj !== false) { + return array($file_obj, $name); + } else { + Auth_OpenID_FileStore::_removeIfPresent($name); + } + } + + function cleanupNonces() + { + global $Auth_OpenID_SKEW; + + $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); + $now = time(); + + $removed = 0; + // Check all nonces for expiry + foreach ($nonces as $nonce_fname) { + $base = basename($nonce_fname); + $parts = explode('-', $base, 2); + $timestamp = $parts[0]; + $timestamp = intval($timestamp, 16); + if (abs($timestamp - $now) > $Auth_OpenID_SKEW) { + Auth_OpenID_FileStore::_removeIfPresent($nonce_fname); + $removed += 1; + } + } + return $removed; + } + + /** + * Create a unique filename for a given server url and + * handle. This implementation does not assume anything about the + * format of the handle. The filename that is returned will + * contain the domain name from the server URL for ease of human + * inspection of the data directory. + * + * @return string $filename + */ + function getAssociationFilename($server_url, $handle) + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + if (strpos($server_url, '://') === false) { + trigger_error(sprintf("Bad server URL: %s", $server_url), + E_USER_WARNING); + return null; + } + + list($proto, $rest) = explode('://', $server_url, 2); + $parts = explode('/', $rest); + $domain = Auth_OpenID_FileStore::_filenameEscape($parts[0]); + $url_hash = Auth_OpenID_FileStore::_safe64($server_url); + if ($handle) { + $handle_hash = Auth_OpenID_FileStore::_safe64($handle); + } else { + $handle_hash = ''; + } + + $filename = sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash, + $handle_hash); + + return $this->association_dir. DIRECTORY_SEPARATOR . $filename; + } + + /** + * Store an association in the association directory. + */ + function storeAssociation($server_url, $association) + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return false; + } + + $association_s = $association->serialize(); + $filename = $this->getAssociationFilename($server_url, + $association->handle); + list($tmp_file, $tmp) = $this->_mktemp(); + + if (!$tmp_file) { + trigger_error("_mktemp didn't return a valid file descriptor", + E_USER_WARNING); + return false; + } + + fwrite($tmp_file, $association_s); + + fflush($tmp_file); + + fclose($tmp_file); + + if (@rename($tmp, $filename)) { + return true; + } else { + // In case we are running on Windows, try unlinking the + // file in case it exists. + @unlink($filename); + + // Now the target should not exist. Try renaming again, + // giving up if it fails. + if (@rename($tmp, $filename)) { + return true; + } + } + + // If there was an error, don't leave the temporary file + // around. + Auth_OpenID_FileStore::_removeIfPresent($tmp); + return false; + } + + /** + * Retrieve an association. If no handle is specified, return the + * association with the most recent issue time. + * + * @return mixed $association + */ + function getAssociation($server_url, $handle = null) + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + if ($handle === null) { + $handle = ''; + } + + // The filename with the empty handle is a prefix of all other + // associations for the given server URL. + $filename = $this->getAssociationFilename($server_url, $handle); + + if ($handle) { + return $this->_getAssociation($filename); + } else { + $association_files = + Auth_OpenID_FileStore::_listdir($this->association_dir); + $matching_files = array(); + + // strip off the path to do the comparison + $name = basename($filename); + foreach ($association_files as $association_file) { + $base = basename($association_file); + if (strpos($base, $name) === 0) { + $matching_files[] = $association_file; + } + } + + $matching_associations = array(); + // read the matching files and sort by time issued + foreach ($matching_files as $full_name) { + $association = $this->_getAssociation($full_name); + if ($association !== null) { + $matching_associations[] = array($association->issued, + $association); + } + } + + $issued = array(); + $assocs = array(); + foreach ($matching_associations as $key => $assoc) { + $issued[$key] = $assoc[0]; + $assocs[$key] = $assoc[1]; + } + + array_multisort($issued, SORT_DESC, $assocs, SORT_DESC, + $matching_associations); + + // return the most recently issued one. + if ($matching_associations) { + list($issued, $assoc) = $matching_associations[0]; + return $assoc; + } else { + return null; + } + } + } + + /** + * @access private + */ + function _getAssociation($filename) + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + $assoc_file = @fopen($filename, 'rb'); + + if ($assoc_file === false) { + return null; + } + + $assoc_s = fread($assoc_file, filesize($filename)); + fclose($assoc_file); + + if (!$assoc_s) { + return null; + } + + $association = + Auth_OpenID_Association::deserialize('Auth_OpenID_Association', + $assoc_s); + + if (!$association) { + Auth_OpenID_FileStore::_removeIfPresent($filename); + return null; + } + + if ($association->getExpiresIn() == 0) { + Auth_OpenID_FileStore::_removeIfPresent($filename); + return null; + } else { + return $association; + } + } + + /** + * Remove an association if it exists. Do nothing if it does not. + * + * @return bool $success + */ + function removeAssociation($server_url, $handle) + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + $assoc = $this->getAssociation($server_url, $handle); + if ($assoc === null) { + return false; + } else { + $filename = $this->getAssociationFilename($server_url, $handle); + return Auth_OpenID_FileStore::_removeIfPresent($filename); + } + } + + /** + * Return whether this nonce is present. As a side effect, mark it + * as no longer present. + * + * @return bool $present + */ + function useNonce($server_url, $timestamp, $salt) + { + global $Auth_OpenID_SKEW; + + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) { + return False; + } + + if ($server_url) { + list($proto, $rest) = explode('://', $server_url, 2); + } else { + $proto = ''; + $rest = ''; + } + + $parts = explode('/', $rest, 2); + $domain = $this->_filenameEscape($parts[0]); + $url_hash = $this->_safe64($server_url); + $salt_hash = $this->_safe64($salt); + + $filename = sprintf('%08x-%s-%s-%s-%s', $timestamp, $proto, + $domain, $url_hash, $salt_hash); + $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $filename; + + $result = @fopen($filename, 'x'); + + if ($result === false) { + return false; + } else { + fclose($result); + return true; + } + } + + /** + * Remove expired entries from the database. This is potentially + * expensive, so only run when it is acceptable to take time. + * + * @access private + */ + function _allAssocs() + { + $all_associations = array(); + + $association_filenames = + Auth_OpenID_FileStore::_listdir($this->association_dir); + + foreach ($association_filenames as $association_filename) { + $association_file = fopen($association_filename, 'rb'); + + if ($association_file !== false) { + $assoc_s = fread($association_file, + filesize($association_filename)); + fclose($association_file); + + // Remove expired or corrupted associations + $association = + Auth_OpenID_Association::deserialize( + 'Auth_OpenID_Association', $assoc_s); + + if ($association === null) { + Auth_OpenID_FileStore::_removeIfPresent( + $association_filename); + } else { + if ($association->getExpiresIn() == 0) { + $all_associations[] = array($association_filename, + $association); + } + } + } + } + + return $all_associations; + } + + function clean() + { + if (!$this->active) { + trigger_error("FileStore no longer active", E_USER_ERROR); + return null; + } + + $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); + $now = time(); + + // Check all nonces for expiry + foreach ($nonces as $nonce) { + if (!Auth_OpenID_checkTimestamp($nonce, $now)) { + $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce; + Auth_OpenID_FileStore::_removeIfPresent($filename); + } + } + + foreach ($this->_allAssocs() as $pair) { + list($assoc_filename, $assoc) = $pair; + if ($assoc->getExpiresIn() == 0) { + Auth_OpenID_FileStore::_removeIfPresent($assoc_filename); + } + } + } + + /** + * @access private + */ + function _rmtree($dir) + { + if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR) { + $dir .= DIRECTORY_SEPARATOR; + } + + if ($handle = opendir($dir)) { + while ($item = readdir($handle)) { + if (!in_array($item, array('.', '..'))) { + if (is_dir($dir . $item)) { + + if (!Auth_OpenID_FileStore::_rmtree($dir . $item)) { + return false; + } + } else if (is_file($dir . $item)) { + if (!unlink($dir . $item)) { + return false; + } + } + } + } + + closedir($handle); + + if (!@rmdir($dir)) { + return false; + } + + return true; + } else { + // Couldn't open directory. + return false; + } + } + + /** + * @access private + */ + function _mkstemp($dir) + { + foreach (range(0, 4) as $i) { + $name = tempnam($dir, "php_openid_filestore_"); + + if ($name !== false) { + return $name; + } + } + return false; + } + + /** + * @access private + */ + function _mkdtemp($dir) + { + foreach (range(0, 4) as $i) { + $name = $dir . strval(DIRECTORY_SEPARATOR) . strval(getmypid()) . + "-" . strval(rand(1, time())); + if (!mkdir($name, 0700)) { + return false; + } else { + return $name; + } + } + return false; + } + + /** + * @access private + */ + function _listdir($dir) + { + $handle = opendir($dir); + $files = array(); + while (false !== ($filename = readdir($handle))) { + if (!in_array($filename, array('.', '..'))) { + $files[] = $dir . DIRECTORY_SEPARATOR . $filename; + } + } + return $files; + } + + /** + * @access private + */ + function _isFilenameSafe($char) + { + $_Auth_OpenID_filename_allowed = Auth_OpenID_letters . + Auth_OpenID_digits . "."; + return (strpos($_Auth_OpenID_filename_allowed, $char) !== false); + } + + /** + * @access private + */ + function _safe64($str) + { + $h64 = base64_encode(Auth_OpenID_SHA1($str)); + $h64 = str_replace('+', '_', $h64); + $h64 = str_replace('/', '.', $h64); + $h64 = str_replace('=', '', $h64); + return $h64; + } + + /** + * @access private + */ + function _filenameEscape($str) + { + $filename = ""; + $b = Auth_OpenID::toBytes($str); + + for ($i = 0; $i < count($b); $i++) { + $c = $b[$i]; + if (Auth_OpenID_FileStore::_isFilenameSafe($c)) { + $filename .= $c; + } else { + $filename .= sprintf("_%02X", ord($c)); + } + } + return $filename; + } + + /** + * Attempt to remove a file, returning whether the file existed at + * the time of the call. + * + * @access private + * @return bool $result True if the file was present, false if not. + */ + function _removeIfPresent($filename) + { + return @unlink($filename); + } + + function cleanupAssociations() + { + $removed = 0; + foreach ($this->_allAssocs() as $pair) { + list($assoc_filename, $assoc) = $pair; + if ($assoc->getExpiresIn() == 0) { + $this->_removeIfPresent($assoc_filename); + $removed += 1; + } + } + return $removed; + } +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/HMAC.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/HMAC.php new file mode 100644 index 00000000..69520097 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/HMAC.php @@ -0,0 +1,102 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +require_once 'Auth/OpenID.php'; + +/** + * SHA1_BLOCKSIZE is this module's SHA1 blocksize used by the fallback + * implementation. + */ +define('Auth_OpenID_SHA1_BLOCKSIZE', 64); + +function Auth_OpenID_SHA1($text) +{ + if (function_exists('hash') && + function_exists('hash_algos') && + (in_array('sha1', hash_algos()))) { + // PHP 5 case (sometimes): 'hash' available and 'sha1' algo + // supported. + return hash('sha1', $text, true); + } else if (function_exists('sha1')) { + // PHP 4 case: 'sha1' available. + $hex = sha1($text); + $raw = ''; + for ($i = 0; $i < 40; $i += 2) { + $hexcode = substr($hex, $i, 2); + $charcode = (int)base_convert($hexcode, 16, 10); + $raw .= chr($charcode); + } + return $raw; + } else { + // Explode. + trigger_error('No SHA1 function found', E_USER_ERROR); + } +} + +/** + * Compute an HMAC/SHA1 hash. + * + * @access private + * @param string $key The HMAC key + * @param string $text The message text to hash + * @return string $mac The MAC + */ +function Auth_OpenID_HMACSHA1($key, $text) +{ + if (Auth_OpenID::bytes($key) > Auth_OpenID_SHA1_BLOCKSIZE) { + $key = Auth_OpenID_SHA1($key, true); + } + + $key = str_pad($key, Auth_OpenID_SHA1_BLOCKSIZE, chr(0x00)); + $ipad = str_repeat(chr(0x36), Auth_OpenID_SHA1_BLOCKSIZE); + $opad = str_repeat(chr(0x5c), Auth_OpenID_SHA1_BLOCKSIZE); + $hash1 = Auth_OpenID_SHA1(($key ^ $ipad) . $text, true); + $hmac = Auth_OpenID_SHA1(($key ^ $opad) . $hash1, true); + return $hmac; +} + +if (function_exists('hash') && + function_exists('hash_algos') && + (in_array('sha256', hash_algos()))) { + function Auth_OpenID_SHA256($text) + { + // PHP 5 case: 'hash' available and 'sha256' algo supported. + return hash('sha256', $text, true); + } + define('Auth_OpenID_SHA256_SUPPORTED', true); +} else { + define('Auth_OpenID_SHA256_SUPPORTED', false); +} + +if (function_exists('hash_hmac') && + function_exists('hash_algos') && + (in_array('sha256', hash_algos()))) { + + function Auth_OpenID_HMACSHA256($key, $text) + { + // Return raw MAC (not hex string). + return hash_hmac('sha256', $text, $key, true); + } + + define('Auth_OpenID_HMACSHA256_SUPPORTED', true); +} else { + define('Auth_OpenID_HMACSHA256_SUPPORTED', false); +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Interface.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Interface.php new file mode 100644 index 00000000..6ee48bc9 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Interface.php @@ -0,0 +1,200 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * This is the interface for the store objects the OpenID library + * uses. It is a single class that provides all of the persistence + * mechanisms that the OpenID library needs, for both servers and + * consumers. If you want to create an SQL-driven store, please see + * then {@link Auth_OpenID_SQLStore} class. + * + * Change: Version 2.0 removed the storeNonce, getAuthKey, and isDumb + * methods, and changed the behavior of the useNonce method to support + * one-way nonces. + * + * @package OpenID + * @author JanRain, Inc. + */ +class Auth_OpenID_OpenIDStore { + /** + * This method puts an Association object into storage, + * retrievable by server URL and handle. + * + * @param string $server_url The URL of the identity server that + * this association is with. Because of the way the server portion + * of the library uses this interface, don't assume there are any + * limitations on the character set of the input string. In + * particular, expect to see unescaped non-url-safe characters in + * the server_url field. + * + * @param Association $association The Association to store. + */ + function storeAssociation($server_url, $association) + { + trigger_error("Auth_OpenID_OpenIDStore::storeAssociation ". + "not implemented", E_USER_ERROR); + } + + /* + * Remove expired nonces from the store. + * + * Discards any nonce from storage that is old enough that its + * timestamp would not pass useNonce(). + * + * This method is not called in the normal operation of the + * library. It provides a way for store admins to keep their + * storage from filling up with expired data. + * + * @return the number of nonces expired + */ + function cleanupNonces() + { + trigger_error("Auth_OpenID_OpenIDStore::cleanupNonces ". + "not implemented", E_USER_ERROR); + } + + /* + * Remove expired associations from the store. + * + * This method is not called in the normal operation of the + * library. It provides a way for store admins to keep their + * storage from filling up with expired data. + * + * @return the number of associations expired. + */ + function cleanupAssociations() + { + trigger_error("Auth_OpenID_OpenIDStore::cleanupAssociations ". + "not implemented", E_USER_ERROR); + } + + /* + * Shortcut for cleanupNonces(), cleanupAssociations(). + * + * This method is not called in the normal operation of the + * library. It provides a way for store admins to keep their + * storage from filling up with expired data. + */ + function cleanup() + { + return array($this->cleanupNonces(), + $this->cleanupAssociations()); + } + + /** + * Report whether this storage supports cleanup + */ + function supportsCleanup() + { + return true; + } + + /** + * This method returns an Association object from storage that + * matches the server URL and, if specified, handle. It returns + * null if no such association is found or if the matching + * association is expired. + * + * If no handle is specified, the store may return any association + * which matches the server URL. If multiple associations are + * valid, the recommended return value for this method is the one + * most recently issued. + * + * This method is allowed (and encouraged) to garbage collect + * expired associations when found. This method must not return + * expired associations. + * + * @param string $server_url The URL of the identity server to get + * the association for. Because of the way the server portion of + * the library uses this interface, don't assume there are any + * limitations on the character set of the input string. In + * particular, expect to see unescaped non-url-safe characters in + * the server_url field. + * + * @param mixed $handle This optional parameter is the handle of + * the specific association to get. If no specific handle is + * provided, any valid association matching the server URL is + * returned. + * + * @return Association The Association for the given identity + * server. + */ + function getAssociation($server_url, $handle = null) + { + trigger_error("Auth_OpenID_OpenIDStore::getAssociation ". + "not implemented", E_USER_ERROR); + } + + /** + * This method removes the matching association if it's found, and + * returns whether the association was removed or not. + * + * @param string $server_url The URL of the identity server the + * association to remove belongs to. Because of the way the server + * portion of the library uses this interface, don't assume there + * are any limitations on the character set of the input + * string. In particular, expect to see unescaped non-url-safe + * characters in the server_url field. + * + * @param string $handle This is the handle of the association to + * remove. If there isn't an association found that matches both + * the given URL and handle, then there was no matching handle + * found. + * + * @return mixed Returns whether or not the given association existed. + */ + function removeAssociation($server_url, $handle) + { + trigger_error("Auth_OpenID_OpenIDStore::removeAssociation ". + "not implemented", E_USER_ERROR); + } + + /** + * Called when using a nonce. + * + * This method should return C{True} if the nonce has not been + * used before, and store it for a while to make sure nobody + * tries to use the same value again. If the nonce has already + * been used, return C{False}. + * + * Change: In earlier versions, round-trip nonces were used and a + * nonce was only valid if it had been previously stored with + * storeNonce. Version 2.0 uses one-way nonces, requiring a + * different implementation here that does not depend on a + * storeNonce call. (storeNonce is no longer part of the + * interface. + * + * @param string $nonce The nonce to use. + * + * @return bool Whether or not the nonce was valid. + */ + function useNonce($server_url, $timestamp, $salt) + { + trigger_error("Auth_OpenID_OpenIDStore::useNonce ". + "not implemented", E_USER_ERROR); + } + + /** + * Removes all entries from the store; implementation is optional. + */ + function reset() + { + } + +} +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/KVForm.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/KVForm.php new file mode 100644 index 00000000..074eb0c9 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/KVForm.php @@ -0,0 +1,115 @@ + + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Container for key-value/comma-newline OpenID format and parsing + */ +class Auth_OpenID_KVForm { + /** + * Convert an OpenID colon/newline separated string into an + * associative array + * + * @static + * @access private + */ + function toArray($kvs, $strict=false) + { + $lines = explode("\n", $kvs); + + $last = array_pop($lines); + if ($last !== '') { + array_push($lines, $last); + if ($strict) { + return false; + } + } + + $values = array(); + + for ($lineno = 0; $lineno < count($lines); $lineno++) { + $line = $lines[$lineno]; + $kv = explode(':', $line, 2); + if (count($kv) != 2) { + if ($strict) { + return false; + } + continue; + } + + $key = $kv[0]; + $tkey = trim($key); + if ($tkey != $key) { + if ($strict) { + return false; + } + } + + $value = $kv[1]; + $tval = trim($value); + if ($tval != $value) { + if ($strict) { + return false; + } + } + + $values[$tkey] = $tval; + } + + return $values; + } + + /** + * Convert an array into an OpenID colon/newline separated string + * + * @static + * @access private + */ + function fromArray($values) + { + if ($values === null) { + return null; + } + + ksort($values); + + $serialized = ''; + foreach ($values as $key => $value) { + if (is_array($value)) { + list($key, $value) = array($value[0], $value[1]); + } + + if (strpos($key, ':') !== false) { + return null; + } + + if (strpos($key, "\n") !== false) { + return null; + } + + if (strpos($value, "\n") !== false) { + return null; + } + $serialized .= "$key:$value\n"; + } + return $serialized; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/MemcachedStore.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/MemcachedStore.php new file mode 100644 index 00000000..59c73bd2 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/MemcachedStore.php @@ -0,0 +1,211 @@ + + * @copyright 2008 JanRain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + * Contributed by Open Web Technologies + */ + +// Do not allow direct access +defined( '_JEXEC' ) or die( 'Restricted access' ); + +/** + * Import the interface for creating a new store class. + */ +require_once 'Auth/OpenID/Interface.php'; + +/** + * This is a memcached-based store for OpenID associations and + * nonces. + * + * As memcache has limit of 250 chars for key length, + * server_url, handle and salt are hashed with sha1(). + * + * Most of the methods of this class are implementation details. + * People wishing to just use this store need only pay attention to + * the constructor. + * + * @package OpenID + */ +class Auth_OpenID_MemcachedStore extends Auth_OpenID_OpenIDStore { + + /** + * Initializes a new {@link Auth_OpenID_MemcachedStore} instance. + * Just saves memcached object as property. + * + * @param resource connection Memcache connection resourse + */ + function Auth_OpenID_MemcachedStore($connection, $compress = false) + { + $this->connection = $connection; + $this->compress = $compress ? MEMCACHE_COMPRESSED : 0; + } + + /** + * Store association until its expiration time in memcached. + * Overwrites any existing association with same server_url and + * handle. Handles list of associations for every server. + */ + function storeAssociation($server_url, $association) + { + // create memcached keys for association itself + // and list of associations for this server + $associationKey = $this->associationKey($server_url, + $association->handle); + $serverKey = $this->associationServerKey($server_url); + + // get list of associations + $serverAssociations = $this->connection->get($serverKey); + + // if no such list, initialize it with empty array + if (!$serverAssociations) { + $serverAssociations = array(); + } + // and store given association key in it + $serverAssociations[$association->issued] = $associationKey; + + // save associations' keys list + $this->connection->set( + $serverKey, + $serverAssociations, + $this->compress + ); + // save association itself + $this->connection->set( + $associationKey, + $association, + $this->compress, + $association->issued + $association->lifetime); + } + + /** + * Read association from memcached. If no handle given + * and multiple associations found, returns latest issued + */ + function getAssociation($server_url, $handle = null) + { + // simple case: handle given + if ($handle !== null) { + // get association, return null if failed + $association = $this->connection->get( + $this->associationKey($server_url, $handle)); + return $association ? $association : null; + } + + // no handle given, working with list + // create key for list of associations + $serverKey = $this->associationServerKey($server_url); + + // get list of associations + $serverAssociations = $this->connection->get($serverKey); + // return null if failed or got empty list + if (!$serverAssociations) { + return null; + } + + // get key of most recently issued association + $keys = array_keys($serverAssociations); + sort($keys); + $lastKey = $serverAssociations[array_pop($keys)]; + + // get association, return null if failed + $association = $this->connection->get($lastKey); + return $association ? $association : null; + } + + /** + * Immediately delete association from memcache. + */ + function removeAssociation($server_url, $handle) + { + // create memcached keys for association itself + // and list of associations for this server + $serverKey = $this->associationServerKey($server_url); + $associationKey = $this->associationKey($server_url, + $handle); + + // get list of associations + $serverAssociations = $this->connection->get($serverKey); + // return null if failed or got empty list + if (!$serverAssociations) { + return false; + } + + // ensure that given association key exists in list + $serverAssociations = array_flip($serverAssociations); + if (!array_key_exists($associationKey, $serverAssociations)) { + return false; + } + + // remove given association key from list + unset($serverAssociations[$associationKey]); + $serverAssociations = array_flip($serverAssociations); + + // save updated list + $this->connection->set( + $serverKey, + $serverAssociations, + $this->compress + ); + + // delete association + return $this->connection->delete($associationKey); + } + + /** + * Create nonce for server and salt, expiring after + * $Auth_OpenID_SKEW seconds. + */ + function useNonce($server_url, $timestamp, $salt) + { + global $Auth_OpenID_SKEW; + + // save one request to memcache when nonce obviously expired + if (abs($timestamp - time()) > $Auth_OpenID_SKEW) { + return false; + } + + // returns false when nonce already exists + // otherwise adds nonce + return $this->connection->add( + 'openid_nonce_' . sha1($server_url) . '_' . sha1($salt), + 1, // any value here + $this->compress, + $Auth_OpenID_SKEW); + } + + /** + * Memcache key is prefixed with 'openid_association_' string. + */ + function associationKey($server_url, $handle = null) + { + return 'openid_association_' . sha1($server_url) . '_' . sha1($handle); + } + + /** + * Memcache key is prefixed with 'openid_association_' string. + */ + function associationServerKey($server_url) + { + return 'openid_association_server_' . sha1($server_url); + } + + /** + * Report that this storage doesn't support cleanup + */ + function supportsCleanup() + { + return false; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Message.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Message.php new file mode 100644 index 00000000..a142ac1f --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Message.php @@ -0,0 +1,918 @@ +keys = array(); + $this->values = array(); + + if (is_array($classic_array)) { + foreach ($classic_array as $key => $value) { + $this->set($key, $value); + } + } + } + + /** + * Returns true if $thing is an Auth_OpenID_Mapping object; false + * if not. + */ + function isA($thing) + { + return (is_object($thing) && + strtolower(get_class($thing)) == 'auth_openid_mapping'); + } + + /** + * Returns an array of the keys in the mapping. + */ + function keys() + { + return $this->keys; + } + + /** + * Returns an array of values in the mapping. + */ + function values() + { + return $this->values; + } + + /** + * Returns an array of (key, value) pairs in the mapping. + */ + function items() + { + $temp = array(); + + for ($i = 0; $i < count($this->keys); $i++) { + $temp[] = array($this->keys[$i], + $this->values[$i]); + } + return $temp; + } + + /** + * Returns the "length" of the mapping, or the number of keys. + */ + function len() + { + return count($this->keys); + } + + /** + * Sets a key-value pair in the mapping. If the key already + * exists, its value is replaced with the new value. + */ + function set($key, $value) + { + $index = array_search($key, $this->keys); + + if ($index !== false) { + $this->values[$index] = $value; + } else { + $this->keys[] = $key; + $this->values[] = $value; + } + } + + /** + * Gets a specified value from the mapping, associated with the + * specified key. If the key does not exist in the mapping, + * $default is returned instead. + */ + function get($key, $default = null) + { + $index = array_search($key, $this->keys); + + if ($index !== false) { + return $this->values[$index]; + } else { + return $default; + } + } + + /** + * @access private + */ + function _reflow() + { + // PHP is broken yet again. Sort the arrays to remove the + // hole in the numeric indexes that make up the array. + $old_keys = $this->keys; + $old_values = $this->values; + + $this->keys = array(); + $this->values = array(); + + foreach ($old_keys as $k) { + $this->keys[] = $k; + } + + foreach ($old_values as $v) { + $this->values[] = $v; + } + } + + /** + * Deletes a key-value pair from the mapping with the specified + * key. + */ + function del($key) + { + $index = array_search($key, $this->keys); + + if ($index !== false) { + unset($this->keys[$index]); + unset($this->values[$index]); + $this->_reflow(); + return true; + } + return false; + } + + /** + * Returns true if the specified value has a key in the mapping; + * false if not. + */ + function contains($value) + { + return (array_search($value, $this->keys) !== false); + } +} + +/** + * Maintains a bijective map between namespace uris and aliases. + * + * @package OpenID + */ +class Auth_OpenID_NamespaceMap { + function Auth_OpenID_NamespaceMap() + { + $this->alias_to_namespace = new Auth_OpenID_Mapping(); + $this->namespace_to_alias = new Auth_OpenID_Mapping(); + $this->implicit_namespaces = array(); + } + + function getAlias($namespace_uri) + { + return $this->namespace_to_alias->get($namespace_uri); + } + + function getNamespaceURI($alias) + { + return $this->alias_to_namespace->get($alias); + } + + function iterNamespaceURIs() + { + // Return an iterator over the namespace URIs + return $this->namespace_to_alias->keys(); + } + + function iterAliases() + { + // Return an iterator over the aliases""" + return $this->alias_to_namespace->keys(); + } + + function iteritems() + { + return $this->namespace_to_alias->items(); + } + + function isImplicit($namespace_uri) + { + return in_array($namespace_uri, $this->implicit_namespaces); + } + + function addAlias($namespace_uri, $desired_alias, $implicit=false) + { + // Add an alias from this namespace URI to the desired alias + global $Auth_OpenID_OPENID_PROTOCOL_FIELDS; + + // Check that desired_alias is not an openid protocol field as + // per the spec. + if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) { + Auth_OpenID::log("\"%s\" is not an allowed namespace alias", + $desired_alias); + return null; + } + + // Check that desired_alias does not contain a period as per + // the spec. + if (strpos($desired_alias, '.') !== false) { + Auth_OpenID::log('"%s" must not contain a dot', $desired_alias); + return null; + } + + // Check that there is not a namespace already defined for the + // desired alias + $current_namespace_uri = + $this->alias_to_namespace->get($desired_alias); + + if (($current_namespace_uri !== null) && + ($current_namespace_uri != $namespace_uri)) { + Auth_OpenID::log('Cannot map "%s" because previous mapping exists', + $namespace_uri); + return null; + } + + // Check that there is not already a (different) alias for + // this namespace URI + $alias = $this->namespace_to_alias->get($namespace_uri); + + if (($alias !== null) && ($alias != $desired_alias)) { + Auth_OpenID::log('Cannot map %s to alias %s. ' . + 'It is already mapped to alias %s', + $namespace_uri, $desired_alias, $alias); + return null; + } + + assert((Auth_OpenID_NULL_NAMESPACE === $desired_alias) || + is_string($desired_alias)); + + $this->alias_to_namespace->set($desired_alias, $namespace_uri); + $this->namespace_to_alias->set($namespace_uri, $desired_alias); + if ($implicit) { + array_push($this->implicit_namespaces, $namespace_uri); + } + + return $desired_alias; + } + + function add($namespace_uri) + { + // Add this namespace URI to the mapping, without caring what + // alias it ends up with + + // See if this namespace is already mapped to an alias + $alias = $this->namespace_to_alias->get($namespace_uri); + + if ($alias !== null) { + return $alias; + } + + // Fall back to generating a numerical alias + $i = 0; + while (1) { + $alias = 'ext' . strval($i); + if ($this->addAlias($namespace_uri, $alias) === null) { + $i += 1; + } else { + return $alias; + } + } + + // Should NEVER be reached! + return null; + } + + function contains($namespace_uri) + { + return $this->isDefined($namespace_uri); + } + + function isDefined($namespace_uri) + { + return $this->namespace_to_alias->contains($namespace_uri); + } +} + +/** + * In the implementation of this object, null represents the global + * namespace as well as a namespace with no key. + * + * @package OpenID + */ +class Auth_OpenID_Message { + + function Auth_OpenID_Message($openid_namespace = null) + { + // Create an empty Message + $this->allowed_openid_namespaces = array( + Auth_OpenID_OPENID1_NS, + Auth_OpenID_THE_OTHER_OPENID1_NS, + Auth_OpenID_OPENID2_NS); + + $this->args = new Auth_OpenID_Mapping(); + $this->namespaces = new Auth_OpenID_NamespaceMap(); + if ($openid_namespace === null) { + $this->_openid_ns_uri = null; + } else { + $implicit = Auth_OpenID_isOpenID1($openid_namespace); + $this->setOpenIDNamespace($openid_namespace, $implicit); + } + } + + function isOpenID1() + { + return Auth_OpenID_isOpenID1($this->getOpenIDNamespace()); + } + + function isOpenID2() + { + return $this->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS; + } + + function fromPostArgs($args) + { + // Construct a Message containing a set of POST arguments + $obj = new Auth_OpenID_Message(); + + // Partition into "openid." args and bare args + $openid_args = array(); + foreach ($args as $key => $value) { + + if (is_array($value)) { + return null; + } + + $parts = explode('.', $key, 2); + + if (count($parts) == 2) { + list($prefix, $rest) = $parts; + } else { + $prefix = null; + } + + if ($prefix != 'openid') { + $obj->args->set(array(Auth_OpenID_BARE_NS, $key), $value); + } else { + $openid_args[$rest] = $value; + } + } + + if ($obj->_fromOpenIDArgs($openid_args)) { + return $obj; + } else { + return null; + } + } + + function fromOpenIDArgs($openid_args) + { + // Takes an array. + + // Construct a Message from a parsed KVForm message + $obj = new Auth_OpenID_Message(); + if ($obj->_fromOpenIDArgs($openid_args)) { + return $obj; + } else { + return null; + } + } + + /** + * @access private + */ + function _fromOpenIDArgs($openid_args) + { + global $Auth_OpenID_registered_aliases; + + // Takes an Auth_OpenID_Mapping instance OR an array. + + if (!Auth_OpenID_Mapping::isA($openid_args)) { + $openid_args = new Auth_OpenID_Mapping($openid_args); + } + + $ns_args = array(); + + // Resolve namespaces + foreach ($openid_args->items() as $pair) { + list($rest, $value) = $pair; + + $parts = explode('.', $rest, 2); + + if (count($parts) == 2) { + list($ns_alias, $ns_key) = $parts; + } else { + $ns_alias = Auth_OpenID_NULL_NAMESPACE; + $ns_key = $rest; + } + + if ($ns_alias == 'ns') { + if ($this->namespaces->addAlias($value, $ns_key) === null) { + return false; + } + } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) && + ($ns_key == 'ns')) { + // null namespace + if ($this->setOpenIDNamespace($value, false) === false) { + return false; + } + } else { + $ns_args[] = array($ns_alias, $ns_key, $value); + } + } + + if (!$this->getOpenIDNamespace()) { + if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) === + false) { + return false; + } + } + + // Actually put the pairs into the appropriate namespaces + foreach ($ns_args as $triple) { + list($ns_alias, $ns_key, $value) = $triple; + $ns_uri = $this->namespaces->getNamespaceURI($ns_alias); + if ($ns_uri === null) { + $ns_uri = $this->_getDefaultNamespace($ns_alias); + if ($ns_uri === null) { + + $ns_uri = Auth_OpenID_OPENID_NS; + $ns_key = sprintf('%s.%s', $ns_alias, $ns_key); + } else { + $this->namespaces->addAlias($ns_uri, $ns_alias, true); + } + } + + $this->setArg($ns_uri, $ns_key, $value); + } + + return true; + } + + function _getDefaultNamespace($mystery_alias) + { + global $Auth_OpenID_registered_aliases; + if ($this->isOpenID1()) { + return @$Auth_OpenID_registered_aliases[$mystery_alias]; + } + return null; + } + + function setOpenIDNamespace($openid_ns_uri, $implicit) + { + if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) { + Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri); + return false; + } + + $succeeded = $this->namespaces->addAlias($openid_ns_uri, + Auth_OpenID_NULL_NAMESPACE, + $implicit); + if ($succeeded === false) { + return false; + } + + $this->_openid_ns_uri = $openid_ns_uri; + + return true; + } + + function getOpenIDNamespace() + { + return $this->_openid_ns_uri; + } + + function fromKVForm($kvform_string) + { + // Create a Message from a KVForm string + return Auth_OpenID_Message::fromOpenIDArgs( + Auth_OpenID_KVForm::toArray($kvform_string)); + } + + function copy() + { + return $this; + } + + function toPostArgs() + { + // Return all arguments with openid. in front of namespaced + // arguments. + + $args = array(); + + // Add namespace definitions to the output + foreach ($this->namespaces->iteritems() as $pair) { + list($ns_uri, $alias) = $pair; + if ($this->namespaces->isImplicit($ns_uri)) { + continue; + } + if ($alias == Auth_OpenID_NULL_NAMESPACE) { + $ns_key = 'openid.ns'; + } else { + $ns_key = 'openid.ns.' . $alias; + } + $args[$ns_key] = $ns_uri; + } + + foreach ($this->args->items() as $pair) { + list($ns_parts, $value) = $pair; + list($ns_uri, $ns_key) = $ns_parts; + $key = $this->getKey($ns_uri, $ns_key); + $args[$key] = $value; + } + + return $args; + } + + function toArgs() + { + // Return all namespaced arguments, failing if any + // non-namespaced arguments exist. + $post_args = $this->toPostArgs(); + $kvargs = array(); + foreach ($post_args as $k => $v) { + if (strpos($k, 'openid.') !== 0) { + // raise ValueError( + // 'This message can only be encoded as a POST, because it ' + // 'contains arguments that are not prefixed with "openid."') + return null; + } else { + $kvargs[substr($k, 7)] = $v; + } + } + + return $kvargs; + } + + function toFormMarkup($action_url, $form_tag_attrs = null, + $submit_text = "Continue") + { + $form = "
$attr) { + $form .= sprintf(" %s=\"%s\"", $name, $attr); + } + } + + $form .= ">\n"; + + foreach ($this->toPostArgs() as $name => $value) { + $form .= sprintf( + "\n", + $name, $value); + } + + $form .= sprintf("\n", + $submit_text); + + $form .= "
\n"; + + return $form; + } + + function toURL($base_url) + { + // Generate a GET URL with the parameters in this message + // attached as query parameters. + return Auth_OpenID::appendArgs($base_url, $this->toPostArgs()); + } + + function toKVForm() + { + // Generate a KVForm string that contains the parameters in + // this message. This will fail if the message contains + // arguments outside of the 'openid.' prefix. + return Auth_OpenID_KVForm::fromArray($this->toArgs()); + } + + function toURLEncoded() + { + // Generate an x-www-urlencoded string + $args = array(); + + foreach ($this->toPostArgs() as $k => $v) { + $args[] = array($k, $v); + } + + sort($args); + return Auth_OpenID::httpBuildQuery($args); + } + + /** + * @access private + */ + function _fixNS($namespace) + { + // Convert an input value into the internally used values of + // this object + + if ($namespace == Auth_OpenID_OPENID_NS) { + if ($this->_openid_ns_uri === null) { + return new Auth_OpenID_FailureResponse(null, + 'OpenID namespace not set'); + } else { + $namespace = $this->_openid_ns_uri; + } + } + + if (($namespace != Auth_OpenID_BARE_NS) && + (!is_string($namespace))) { + //TypeError + $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ". + "Auth_OpenID_OPENID_NS or a string. got %s", + print_r($namespace, true)); + return new Auth_OpenID_FailureResponse(null, $err_msg); + } + + if (($namespace != Auth_OpenID_BARE_NS) && + (strpos($namespace, ':') === false)) { + // fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r' + // warnings.warn(fmt % (namespace,), DeprecationWarning) + + if ($namespace == 'sreg') { + // fmt = 'Using %r instead of "sreg" as namespace' + // warnings.warn(fmt % (SREG_URI,), DeprecationWarning,) + return Auth_OpenID_SREG_URI; + } + } + + return $namespace; + } + + function hasKey($namespace, $ns_key) + { + $namespace = $this->_fixNS($namespace); + if (Auth_OpenID::isFailure($namespace)) { + // XXX log me + return false; + } else { + return $this->args->contains(array($namespace, $ns_key)); + } + } + + function getKey($namespace, $ns_key) + { + // Get the key for a particular namespaced argument + $namespace = $this->_fixNS($namespace); + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } + if ($namespace == Auth_OpenID_BARE_NS) { + return $ns_key; + } + + $ns_alias = $this->namespaces->getAlias($namespace); + + // No alias is defined, so no key can exist + if ($ns_alias === null) { + return null; + } + + if ($ns_alias == Auth_OpenID_NULL_NAMESPACE) { + $tail = $ns_key; + } else { + $tail = sprintf('%s.%s', $ns_alias, $ns_key); + } + + return 'openid.' . $tail; + } + + function getArg($namespace, $key, $default = null) + { + // Get a value for a namespaced key. + $namespace = $this->_fixNS($namespace); + + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } else { + if ((!$this->args->contains(array($namespace, $key))) && + ($default == Auth_OpenID_NO_DEFAULT)) { + $err_msg = sprintf("Namespace %s missing required field %s", + $namespace, $key); + return new Auth_OpenID_FailureResponse(null, $err_msg); + } else { + return $this->args->get(array($namespace, $key), $default); + } + } + } + + function getArgs($namespace) + { + // Get the arguments that are defined for this namespace URI + + $namespace = $this->_fixNS($namespace); + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } else { + $stuff = array(); + foreach ($this->args->items() as $pair) { + list($key, $value) = $pair; + list($pair_ns, $ns_key) = $key; + if ($pair_ns == $namespace) { + $stuff[$ns_key] = $value; + } + } + + return $stuff; + } + } + + function updateArgs($namespace, $updates) + { + // Set multiple key/value pairs in one call + + $namespace = $this->_fixNS($namespace); + + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } else { + foreach ($updates as $k => $v) { + $this->setArg($namespace, $k, $v); + } + return true; + } + } + + function setArg($namespace, $key, $value) + { + // Set a single argument in this namespace + $namespace = $this->_fixNS($namespace); + + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } else { + $this->args->set(array($namespace, $key), $value); + if ($namespace !== Auth_OpenID_BARE_NS) { + $this->namespaces->add($namespace); + } + return true; + } + } + + function delArg($namespace, $key) + { + $namespace = $this->_fixNS($namespace); + + if (Auth_OpenID::isFailure($namespace)) { + return $namespace; + } else { + return $this->args->del(array($namespace, $key)); + } + } + + function getAliasedArg($aliased_key, $default = null) + { + $parts = explode('.', $aliased_key, 2); + + if (count($parts) != 2) { + $ns = null; + } else { + list($alias, $key) = $parts; + + if ($alias == 'ns') { + // Return the namespace URI for a namespace alias + // parameter. + return $this->namespaces->getNamespaceURI($key); + } else { + $ns = $this->namespaces->getNamespaceURI($alias); + } + } + + if ($ns === null) { + $key = $aliased_key; + $ns = $this->getOpenIDNamespace(); + } + + return $this->getArg($ns, $key, $default); + } +} + +?> diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/MySQLStore.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/MySQLStore.php new file mode 100644 index 00000000..6e0fd12d --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/MySQLStore.php @@ -0,0 +1,81 @@ +sql['nonce_table'] = + "CREATE TABLE %s (\n". + " server_url VARCHAR(2047) NOT NULL,\n". + " timestamp INTEGER NOT NULL,\n". + " salt CHAR(40) NOT NULL,\n". + " UNIQUE (server_url(255), timestamp, salt)\n". + ") ENGINE=InnoDB"; + + $this->sql['assoc_table'] = + "CREATE TABLE %s (\n". + " server_url BLOB NOT NULL,\n". + " handle VARCHAR(255) NOT NULL,\n". + " secret BLOB NOT NULL,\n". + " issued INTEGER NOT NULL,\n". + " lifetime INTEGER NOT NULL,\n". + " assoc_type VARCHAR(64) NOT NULL,\n". + " PRIMARY KEY (server_url(255), handle)\n". + ") ENGINE=InnoDB"; + + $this->sql['set_assoc'] = + "REPLACE INTO %s (server_url, handle, secret, issued,\n". + " lifetime, assoc_type) VALUES (?, ?, !, ?, ?, ?)"; + + $this->sql['get_assocs'] = + "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ". + "WHERE server_url = ?"; + + $this->sql['get_assoc'] = + "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ". + "WHERE server_url = ? AND handle = ?"; + + $this->sql['remove_assoc'] = + "DELETE FROM %s WHERE server_url = ? AND handle = ?"; + + $this->sql['add_nonce'] = + "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)"; + + $this->sql['clean_nonce'] = + "DELETE FROM %s WHERE timestamp < ?"; + + $this->sql['clean_assoc'] = + "DELETE FROM %s WHERE issued + lifetime < ?"; + } + + /** + * @access private + */ + function blobEncode($blob) + { + return "0x" . bin2hex($blob); + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Nonce.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Nonce.php new file mode 100644 index 00000000..6d38c134 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Nonce.php @@ -0,0 +1,112 @@ + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/PAPE.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/PAPE.php new file mode 100644 index 00000000..0305ed20 --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/PAPE.php @@ -0,0 +1,304 @@ +preferred_auth_policies = $preferred_auth_policies; + $this->max_auth_age = $max_auth_age; + } + + /** + * Add an acceptable authentication policy URI to this request + * + * This method is intended to be used by the relying party to add + * acceptable authentication types to the request. + * + * policy_uri: The identifier for the preferred type of + * authentication. + */ + function addPolicyURI($policy_uri) + { + if (!in_array($policy_uri, $this->preferred_auth_policies)) { + $this->preferred_auth_policies[] = $policy_uri; + } + } + + function getExtensionArgs() + { + $ns_args = array( + 'preferred_auth_policies' => + implode(' ', $this->preferred_auth_policies) + ); + + if ($this->max_auth_age !== null) { + $ns_args['max_auth_age'] = strval($this->max_auth_age); + } + + return $ns_args; + } + + /** + * Instantiate a Request object from the arguments in a checkid_* + * OpenID message + */ + function fromOpenIDRequest($request) + { + $obj = new Auth_OpenID_PAPE_Request(); + $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI); + + if ($args === null || $args === array()) { + return null; + } + + $obj->parseExtensionArgs($args); + return $obj; + } + + /** + * Set the state of this request to be that expressed in these + * PAPE arguments + * + * @param args: The PAPE arguments without a namespace + */ + function parseExtensionArgs($args) + { + // preferred_auth_policies is a space-separated list of policy + // URIs + $this->preferred_auth_policies = array(); + + $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies'); + if ($policies_str) { + foreach (explode(' ', $policies_str) as $uri) { + if (!in_array($uri, $this->preferred_auth_policies)) { + $this->preferred_auth_policies[] = $uri; + } + } + } + + // max_auth_age is base-10 integer number of seconds + $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age'); + if ($max_auth_age_str) { + $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str); + } else { + $this->max_auth_age = null; + } + } + + /** + * Given a list of authentication policy URIs that a provider + * supports, this method returns the subsequence of those types + * that are preferred by the relying party. + * + * @param supported_types: A sequence of authentication policy + * type URIs that are supported by a provider + * + * @return array The sub-sequence of the supported types that are + * preferred by the relying party. This list will be ordered in + * the order that the types appear in the supported_types + * sequence, and may be empty if the provider does not prefer any + * of the supported authentication types. + */ + function preferredTypes($supported_types) + { + $result = array(); + + foreach ($supported_types as $st) { + if (in_array($st, $this->preferred_auth_policies)) { + $result[] = $st; + } + } + return $result; + } +} + +/** + * A Provider Authentication Policy response, sent from a provider to + * a relying party + */ +class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension { + + var $ns_alias = 'pape'; + var $ns_uri = Auth_OpenID_PAPE_NS_URI; + + function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null, + $nist_auth_level=null) + { + if ($auth_policies) { + $this->auth_policies = $auth_policies; + } else { + $this->auth_policies = array(); + } + + $this->auth_time = $auth_time; + $this->nist_auth_level = $nist_auth_level; + } + + /** + * Add a authentication policy to this response + * + * This method is intended to be used by the provider to add a + * policy that the provider conformed to when authenticating the + * user. + * + * @param policy_uri: The identifier for the preferred type of + * authentication. + */ + function addPolicyURI($policy_uri) + { + if (!in_array($policy_uri, $this->auth_policies)) { + $this->auth_policies[] = $policy_uri; + } + } + + /** + * Create an Auth_OpenID_PAPE_Response object from a successful + * OpenID library response. + * + * @param success_response $success_response A SuccessResponse + * from Auth_OpenID_Consumer::complete() + * + * @returns: A provider authentication policy response from the + * data that was supplied with the id_res response. + */ + function fromSuccessResponse($success_response) + { + $obj = new Auth_OpenID_PAPE_Response(); + + // PAPE requires that the args be signed. + $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI); + + if ($args === null || $args === array()) { + return null; + } + + $result = $obj->parseExtensionArgs($args); + + if ($result === false) { + return null; + } else { + return $obj; + } + } + + /** + * Parse the provider authentication policy arguments into the + * internal state of this object + * + * @param args: unqualified provider authentication policy + * arguments + * + * @param strict: Whether to return false when bad data is + * encountered + * + * @return null The data is parsed into the internal fields of + * this object. + */ + function parseExtensionArgs($args, $strict=false) + { + $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies'); + if ($policies_str && $policies_str != "none") { + $this->auth_policies = explode(" ", $policies_str); + } + + $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level'); + if ($nist_level_str !== null) { + $nist_level = Auth_OpenID::intval($nist_level_str); + + if ($nist_level === false) { + if ($strict) { + return false; + } else { + $nist_level = null; + } + } + + if (0 <= $nist_level && $nist_level < 5) { + $this->nist_auth_level = $nist_level; + } else if ($strict) { + return false; + } + } + + $auth_time = Auth_OpenID::arrayGet($args, 'auth_time'); + if ($auth_time !== null) { + if (ereg(PAPE_TIME_VALIDATOR, $auth_time)) { + $this->auth_time = $auth_time; + } else if ($strict) { + return false; + } + } + } + + function getExtensionArgs() + { + $ns_args = array(); + if (count($this->auth_policies) > 0) { + $ns_args['auth_policies'] = implode(' ', $this->auth_policies); + } else { + $ns_args['auth_policies'] = 'none'; + } + + if ($this->nist_auth_level !== null) { + if (!in_array($this->nist_auth_level, range(0, 4), true)) { + return false; + } + $ns_args['nist_auth_level'] = strval($this->nist_auth_level); + } + + if ($this->auth_time !== null) { + if (!ereg(PAPE_TIME_VALIDATOR, $this->auth_time)) { + return false; + } + + $ns_args['auth_time'] = $this->auth_time; + } + + return $ns_args; + } +} + +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/openid/Auth/OpenID/Parse.php b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Parse.php new file mode 100644 index 00000000..1260366a --- /dev/null +++ b/oss/joomla/trunk/libraries/openid/Auth/OpenID/Parse.php @@ -0,0 +1,355 @@ + tags + * in the head of HTML or XHTML documents and parses out their + * attributes according to the OpenID spec. It is a liberal parser, + * but it requires these things from the data in order to work: + * + * - There must be an open tag + * + * - There must be an open tag inside of the tag + * + * - Only s that are found inside of the tag are parsed + * (this is by design) + * + * - The parser follows the OpenID specification in resolving the + * attributes of the link tags. This means that the attributes DO + * NOT get resolved as they would by an XML or HTML parser. In + * particular, only certain entities get replaced, and href + * attributes do not get resolved relative to a base URL. + * + * From http://openid.net/specs.bml: + * + * - The openid.server URL MUST be an absolute URL. OpenID consumers + * MUST NOT attempt to resolve relative URLs. + * + * - The openid.server URL MUST NOT include entities other than &, + * <, >, and ". + * + * The parser ignores SGML comments and . Both kinds + * of quoting are allowed for attributes. + * + * The parser deals with invalid markup in these ways: + * + * - Tag names are not case-sensitive + * + * - The tag is accepted even when it is not at the top level + * + * - The tag is accepted even when it is not a direct child of + * the tag, but a tag must be an ancestor of the + * tag + * + * - tags are accepted even when they are not direct children + * of the tag, but a tag must be an ancestor of the + * tag + * + * - If there is no closing tag for an open or tag, the + * remainder of the document is viewed as being inside of the + * tag. If there is no closing tag for a tag, the link tag is + * treated as a short tag. Exceptions to this rule are that + * closes and or closes + * + * - Attributes of the tag are not required to be quoted. + * + * - In the case of duplicated attribute names, the attribute coming + * last in the tag will be the value returned. + * + * - Any text that does not parse as an attribute within a link tag + * will be ignored. (e.g. will + * ignore pumpkin) + * + * - If there are more than one or tag, the parser only + * looks inside of the first one. + * + * - The contents of '; + echo ' '; + echo ''; + echo '
'; + echo '

patTemplate Dump

'; + } + + /** + * dump the global variables + * + * @access public + * @param array array containing all global variables + */ + function dumpGlobals( $globals ) + { + echo '

Global template variables ('.count( $globals ).')

'; + if( !empty( $globals ) ) + { + echo '
'; + echo ' '; + foreach( $globals as $key => $value ) + { + $this->_displayLine( $key, $value ); + } + echo '
'; + echo '
'; + } + } + + /** + * dump the templates + * + * @access public + * @param array templates + */ + function dumpTemplates( $templates, $vars ) + { + $templates = array_reverse( $templates ); + + echo '

Templates ('.count( $templates ).')   Expand all | Collapse all

'; + echo '
Dumping selected templates...
'; + + foreach( $templates as $name => $tmpl ) + { + if( !isset( $vars[$name] ) ) + $vars[$name] = array(); + + $vars[$name] = $this->_flattenVars( $vars[$name] ); + + echo '
'; + echo '

[+] Template "'.$name.'"

'; + echo '
'; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo '
'; + + $this->_displayAttributes( $tmpl, $name ); + + echo ' '; + + $this->_displayTemplateState( $tmpl ); + + echo ' '; + echo '
Dependencies
'; + + if( !empty( $tmpl['dependencies'] ) ) + { + $dependencies = array(); + foreach( $tmpl['dependencies'] as $dependency ) + array_push( $dependencies, ''.$dependency.'' ); + + echo implode( '
', $dependencies ); + } + else + { + echo '[none]'; + } + + echo '
'; + + + /** + * ------------------------------------------------------------------ + * display variables + */ + $nestedTypes = array( + 'condition', + 'modulo', + ); + + if( in_array( $tmpl['attributes']['type'], $nestedTypes ) ) + { + $content = ''; + + foreach( $tmpl['subtemplates'] as $subName => $subDetails ) + { + $content .= $subDetails['data']; + } + } + else + { + $content = $tmpl['content']; + } + + $setVars = $this->_extractVars( $content ); + + echo '
'; + echo '
Variables
'; + $this->_displayVariables( $tmpl, $name, $setVars, $vars ); + echo '
'; + + if( !empty( $tmpl['comments'] ) ) + { + echo '
'; + echo '
Comments
'; + echo '
'; + echo implode( '
', $tmpl['comments'] ); + echo '
'; + echo '
'; + } + + /** + * ------------------------------------------------------------------ + * display template content + */ + echo '
'; + switch( $tmpl['attributes']['type'] ) + { + case 'condition': + case 'modulo': + $this->_displayConditions( $tmpl, $name, $vars ); + break; + + default: + echo '
Content
'; + echo '
'.$this->_highlightVars( htmlspecialchars( $tmpl['content'] ), $vars[$name] ).'
'; + break; + } + + echo '
'; + echo '
'; + echo '
'; + } + + echo '
'; + } + + function _displayVariables( $tmpl, $tmplName, $setVars, $vars ) + { + if( empty( $setVars ) ) + { + echo '[none]'; + return true; + } + + echo ''; + $this->_useBorders( true ); + $this->_displayHead( 'Name', 'Assigned value', 'Modifier' ); + + foreach( $setVars as $var ) + { + if( isset( $vars[$tmplName][$var] ) ) + $value = $vars[$tmplName][$var]; + else + $value = '[no value set]'; + + if( isset( $tmpl['modifyVars'][$var] ) ) + { + $params = array(); + foreach( $tmpl['modifyVars'][$var]['params'] as $n => $val ) + { + array_push( $params, $n.'="'.$val.'"' ); + } + $modifier = sprintf( '%s( %s )', $tmpl['modifyVars'][$var]['mod'], implode( ', ', $params ) ); + } + else + { + $modifier = '[none]'; + } + + $this->_displayLine( $var, $value, $modifier ); + } + echo '
'; + + $this->_useBorders( false ); + } + + function _displayConditions( $tmpl, $tmplName, $vars ) + { + foreach( $tmpl['subtemplates'] as $cond => $spec ) + { + echo '

[+] Subtemplate "'.$cond.'"

'; + echo '
'; + echo ' '; + + if( !empty( $spec['comments'] ) ) + { + echo '
Comments
'; + echo '
'; + echo implode( '
', $spec['comments'] ); + echo '
'; + } + + echo '
Dependencies
'; + echo '
'; + if( !empty( $spec['dependencies'] ) ) + { + $dependencies = array(); + foreach( $spec['dependencies'] as $dependency ) + array_push( $dependencies, ''.$dependency.'' ); + + echo implode( ', ', $dependencies ); + } + else + { + echo '[none]'; + } + echo '
'; + + echo '
Content
'; + echo '
'.$this->_highlightVars( htmlspecialchars( $spec['data'] ), $vars[$tmplName] ).'
'; + echo '
'; + } + } + + /** + * display the attributes of a template + * + * @access private + * @param array template data + * @param string name of the template + */ + function _displayAttributes( $tmpl, $tmplName = null ) + { + echo '
Attributes
'; + echo ''; + + /** + * type + */ + switch( $tmpl['attributes']['type'] ) + { + case 'modulo': + if( $tmpl['attributes']['modulo'] == 2 ) + $type = 'OddEven'; + else + $type = 'modulo (' . $tmpl['attributes']['modulo'] . ')'; + case 'condition': + if( !isset( $type ) ) + $type = 'condition'; + + $this->_displayLine( 'Type', $tmpl['attributes']['type'] ); + /** + * condition variable, only used in condition templates + */ + if( isset( $tmpl['attributes']['conditionvar'] ) ) + { + if( isset( $tmpl['attributes']['conditiontmpl'] ) ) + $this->_displayLine( 'ConditionVar', $tmpl['attributes']['conditiontmpl'].'.'.$tmpl['attributes']['conditionvar'] ); + elseif( isset( $tmpl['attributes']['useglobals'] ) && $tmpl['attributes']['useglobals'] == 'yes' ) + $this->_displayLine( 'ConditionVar', '__globals.'.$tmpl['attributes']['conditionvar'] ); + else + $this->_displayLine( 'ConditionVar', $tmpl['attributes']['conditionvar'] ); + } + break; + + case 'simplecondition': + $this->_displayLine( 'Type', 'simplecondition' ); + $requiredvars = array(); + foreach ($tmpl['attributes']['requiredvars'] as $tmp) { + if ($tmp[0] !== $tmplName) { + $var = $tmp[0] . '.' . $tmp[1]; + } else { + $var = $tmp[1]; + } + if ($tmp[2] !== null) { + $var = $var . '='.$tmp[2]; + } + array_push($requiredvars, $var); + } + + $this->_displayLine( 'RequiredVars', implode( ', ', $requiredvars ) ); + + break; + default: + $this->_displayLine( 'Type', $tmpl['attributes']['type'] ); + } + + /** + * standard attributes + */ + $this->_displayLine( 'Visibility', $tmpl['attributes']['visibility'] ); + $this->_displayLine( 'WhiteSpace', $tmpl['attributes']['whitespace'] ); + $this->_displayLine( 'AddSystemVars', $tmpl['attributes']['addsystemvars'] ); + $this->_displayLine( 'UnusedVars', $tmpl['attributes']['unusedvars'] ); + + /** + * external source + */ + if( isset( $tmpl['attributes']['src'] ) ) + $this->_displayLine( 'External Src', $tmpl['attributes']['src'] ); + /** + * varscope + */ + if (isset($tmpl['attributes']['varscope'])) { + if (is_array($tmpl['attributes']['varscope'])) { + $this->_displayLine( 'Varscope', implode(', ', $tmpl['attributes']['varscope'] ) ); + } else { + $this->_displayLine( 'Varscope', $tmpl['attributes']['varscope'] ); + } + } + + echo '
'; + } + + function _displayTemplateState( $tmpl ) + { + echo '
States
'; + echo ' '; + $this->_displayLine( 'Loaded', $tmpl['loaded'] ); + $this->_displayLine( 'Parsed', $tmpl['parsed'] ); + echo '
'; + } + + /** + * hilight variables in a template + * + * @access private + * @param string template content + * @return string template content + */ + function _highlightVars( $template, $vars ) + { + $pattern = '/('.$this->_tmpl->getStartTag().'TMPL\:([^a-z]+)'.$this->_tmpl->getEndTag().')/U'; + $template = preg_replace( $pattern, '\1', $template ); + + $pattern = '/('.$this->_tmpl->getStartTag().'([^a-z:]+)'.$this->_tmpl->getEndTag().')/U'; + $matches = array(); + preg_match_all( $pattern, $template, $matches ); + for( $i = 0; $i < count( $matches[1] ); $i++ ) + { + if( isset( $vars[$matches[2][$i]] ) ) + { + $value = $vars[$matches[2][$i]]; + } + else + { + $value = '[No value set]'; + } + $replace = ''.$matches[1][$i].''; + $template = str_replace( $matches[1][$i], $replace, $template ); + } + return $template; + } + + /** + * display a table header + * + * @access private + * @param string property + * @param mixed value, you may pass more than one value + */ + function _displayHead() + { + $args = func_get_args(); + + echo ' '; + foreach( $args as $head ) + { + printf( ' %s', $head ); + } + echo ' '; + } + + function _getClassName( $class ) + { + if( !$this->_useBorders ) + return $class; + + return $class .= 'Border'; + } + + /** + * sets whether to draw borders in the tables generated via the + * {@link _displayHead()} and {@link _displayLine()} methods. + * + * @access private + * @param bool $state Whether to draw the borders. true=draw, false=don't draw + */ + function _useBorders( $state ) + { + $this->_useBorders = $state; + } + + /** + * display a line in a table + * + * @access private + * @param string property + * @param mixed value, you may pass more than one value + */ + function _displayLine( $prop, $value ) + { + $args = func_get_args(); + $prop = array_shift( $args ); + + echo ' '; + printf( ' %s', $prop ); + if( count( $args ) == 1 ) + { + echo '  : '; + } + + foreach( $args as $value ) + { + if( is_bool( $value ) ) + { + $value = ( $value === true ) ? 'yes' : 'no'; + } + + printf( ' %s', $value ); + } + echo ' '; + } + + /** + * display the footer + * + * @access public + */ + function displayFooter() + { + echo '
'; + echo ' '; + echo ''; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/XUL.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/XUL.php new file mode 100644 index 00000000..c8f57183 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/XUL.php @@ -0,0 +1,229 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +require_once 'XML/XUL.php'; + +/** + * Dumps templates as XUL, using PEAR::XML_XUL + * + * @package patTemplate + * @subpackage Dump + * @author Stephan Schmidt + * + * @todo move this into patTemplate_Dump_Dhtml and keep it free from javascript + */ +class patTemplate_Dump_XUL extends patTemplate_Dump +{ + var $_doc = null; + + var $_root = null; + + var $_templates = null; + + var $_addedTemplates = array(); + var $_vars = array(); + + /** + * display the header + * + * @access public + */ + function displayHeader() + { + $this->_addedTemplates = array(); + + $this->_doc = &XML_XUL::createDocument( ); + + $this->_doc->addStylesheet('chrome://global/skin/'); + + $win = &$this->_doc->createElement('Window', array('title'=> 'patTemplate Dump')); + $this->_doc->addRoot($win); + + $this->_root = &$this->_doc->createElement( 'Tabbox', array('flex' => 1) ); + $win->appendChild($this->_root); + + } + + /** + * dump the global variables + * + * @access public + * @param array array containing all global variables + */ + function dumpGlobals( $globals ) + { + $gbox = &$this->_doc->createElement('Groupbox', array('orient'=>'vertical', 'flex' => 1)); + $gbox->setCaption('Global variables'); + + $grid = &$this->_doc->createElement('Grid'); + $grid->setColumns(2, array( 'flex' => 1 ), array( 'flex' => 1 )); + + $gbox->appendChild($grid); + + $headers = array( + $this->_doc->createElement( 'Description', array( 'style' => 'font-weight:bold;' ), 'Variable' ), + $this->_doc->createElement( 'Description', array( 'style' => 'font-weight:bold;' ), 'Value' ), + ); + $grid->addRow($headers); + foreach ($globals as $var => $value) { + $row = array($var, $value); + $grid->addRow($row); + } + $this->_root->addTab('Global Variables', $gbox); + + } + + /** + * dump the templates + * + * @access public + * @param array templates + */ + function dumpTemplates( $templates, $vars ) + { + $container = &$this->_doc->createElement('VBox', array('flex' => 1)); + + $gbox = &$this->_doc->createElement('Groupbox', array('orient'=>'vertical', 'flex' => '2')); + $gbox->setCaption('Templates'); + $container->appendChild($gbox); + + $this->_templates = $templates; + $this->_vars = $vars; + + $templates = array_reverse( $templates ); + + $tree = &$this->_doc->createElement( 'Tree', array( 'flex' => 1, 'enableColumnDrag' => 'true', 'height' => '500' ) ); + $tree->setColumns( 5, + array( + 'id' => 'name', + 'label' => 'Name', + 'flex' => 2, + 'primary' => 'true', + ), + array( + 'id' => 'value', + 'label' => 'Value', + 'flex' => 1, + ), + array( + 'id' => 'type', + 'label' => 'Type', + 'flex' => 1, + ), + array( + 'id' => 'visibility', + 'label' => 'Visibility', + 'flex' => 1, + ), + array( + 'id' => 'loaded', + 'label' => 'Loaded', + 'flex' => 1, + ) + ); + + foreach( $templates as $name => $tmpl ) + { + if (in_array($name, $this->_addedTemplates)) { + continue; + } + $this->_addToTree($name, $tree); + } + + $gbox->appendChild($tree); + + $splitter = &$this->_doc->createElement('Splitter'); + $splitter->useGrippy(); + + $container->appendChild($splitter); + + $gbox2 = &$this->_doc->createElement('Groupbox', array('orient'=>'vertical', 'flex' => '2')); + $gbox2->setCaption('Details'); + + $container->appendChild($gbox2); + + $deck = &$this->_doc->createElement('Deck'); + + $gbox2->appendChild($deck); + + + $this->_root->addTab('Templates', $container); + return true; + } + + function _addToTree($name, &$tree) + { + $tmpl = $this->_getTemplate($name); + $item = array( + $name, + '', + $tmpl['attributes']['type'], + $tmpl['attributes']['visibility'], + $tmpl['loaded'] ? 'yes' : 'no', + ); + $current = &$tree->addItem($item); + array_push($this->_addedTemplates, $name); + if (!empty($tmpl['dependencies'])) { + $deps = &$current->addItem(array( 'Dependencies' )); + foreach ($tmpl['dependencies'] as $dependency) { + $this->_addToTree($dependency, $deps); + } + } + + if (!isset($this->_vars[$name])) { + $this->_vars[$name] = array(); + } + $vars = $this->_flattenVars( $this->_vars[$name] ); + + if (empty($vars)) { + return true; + } + $varItem = &$current->addItem(array( 'Variables' )); + foreach ($vars as $key => $value) { + $varItem->addItem(array($key, $value)); + } + } + + function _getTemplate($name) + { + if (isset($this->_templates[$name])) { + return $this->_templates[$name]; + } + } + + /** + * display the footer + * + * @access public + */ + function displayFooter() + { + if ($_GET['mode'] == 'debug') { + require_once 'XML/Beautifier.php'; + $fmt = &new XML_Beautifier( array( 'indent' => ' ' ) ); + echo '
';
+			echo htmlspecialchars( $fmt->formatString($this->_doc->serialize()) );
+			echo '
'; + } elseif ($_GET['mode'] == 'source') { + highlight_file( __FILE__ ); + } elseif ($_GET['mode'] == 'debug2') { + echo '
';
+			echo htmlspecialchars( $this->_doc->getDebug());
+			echo '
'; + } elseif ($_GET['mode'] == 'source') { } else { + $this->_doc->send(); + } + } +} +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Dump/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function.php new file mode 100644 index 00000000..88edb3bd --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function.php @@ -0,0 +1,77 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * function is executed, when template is compiled (preg_match) + */ +define('PATTEMPLATE_FUNCTION_COMPILE', 1); + +/** + * function is executed, when template parsed + */ +define('PATTEMPLATE_FUNCTION_RUNTIME', 2); + +/** + * Base class for patTemplate functions + * + * $Id: Function.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @abstract + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function extends patTemplate_Module +{ + /** + * reader object + * + * @access private + * @var object + */ + var $_reader; + + /** + * function type + * + * @access public + * @var integer + */ + var $type = PATTEMPLATE_FUNCTION_COMPILE; + + /** + * set the reference to the reader object + * + * @access public + * @param object patTemplate_Reader + */ + function setReader( &$reader ) + { + $this->_reader = &$reader; + } + + /** + * call the function + * + * @abstract + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Alias.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Alias.php new file mode 100644 index 00000000..f37717ff --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Alias.php @@ -0,0 +1,57 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * creates a new function alias + * + * Possible attributes: + * - alias => new alias + * - function => function to call + * + * $Id: Alias.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Alias extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Alias'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + if( !isset( $params['alias'] ) ) + return false; + + if( !isset( $params['function'] ) ) + return false; + + $this->_reader->addFunctionAlias($params['alias'], $params['function']); + return ''; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Attribute.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Attribute.php new file mode 100644 index 00000000..24239491 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Attribute.php @@ -0,0 +1,57 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function to dynamically change the + * value of _any_ attribute of the parent tag. + * + * Possible attributes: + * - name => name of the attribute to change + * + * The enclosed data will be used as the value of the attribute. + * + * $Id: Attribute.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Attribute extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Attribute'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + if( isset( $params['name'] ) ) + { + $this->_reader->_addToParentTag( 'attributes', $content, $params['name'] ); + } + return ''; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Call.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Call.php new file mode 100644 index 00000000..cff836a5 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Call.php @@ -0,0 +1,119 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * template does not exist + */ +define( 'PATTEMPLATE_FUNCTION_CALL_ERROR_NO_TEMPLATE', 'patTemplate::Function::Call::NT' ); + +/** + * patTemplate function that enables you to insert any + * template, that has been loaded previously into the + * current template. + * + * You may pass any variables to the template. + * + * $Id: Call.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Call extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Call'; + + /** + * reference to the patTemplate object that instantiated the module + * + * @access protected + * @var object + */ + var $_tmpl; + + /** + * set a reference to the patTemplate object that instantiated the reader + * + * @access public + * @param object patTemplate object + */ + function setTemplateReference( &$tmpl ) + { + $this->_tmpl = &$tmpl; + } + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + // get the name of the template to use + if (isset($params['template'])) { + $tmpl = $params['template']; + unset( $params['template'] ); + } elseif (isset($params['_originalTag'])) { + $tmpl = $params['_originalTag']; + unset( $params['_originalTag'] ); + } else { + return patErrorManager::raiseError( PATTEMPLATE_FUNCTION_CALL_ERROR_NO_TEMPLATE, 'No template for Call function specified.' ); + } + + if (!$this->_tmpl->exists( $tmpl )) { + + $tmpl = strtolower($tmpl); + + // try some autoloading magic + $componentLocation = $this->_tmpl->getOption('componentFolder'); + $componentExtension = $this->_tmpl->getOption('componentExtension'); + $filename = $componentLocation . '/' . $tmpl . '.' . $componentExtension; + $this->_tmpl->readTemplatesFromInput($filename); + + // still does not exist + if( !$this->_tmpl->exists( $tmpl ) ) { + return patErrorManager::raiseError( PATTEMPLATE_FUNCTION_CALL_ERROR_NO_TEMPLATE, 'Template '.$tmpl.' does not exist' ); + } + } + + /** + * clear template and all of its dependencies + */ + $this->_tmpl->clearTemplate( $tmpl, true ); + + /** + * add variables + */ + $this->_tmpl->addVars( $tmpl, $params ); + $this->_tmpl->addVar( $tmpl, 'CONTENT', $content ); + + /** + * get content + */ + return $this->_tmpl->getParsedTemplate( $tmpl ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Globalvar.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Globalvar.php new file mode 100644 index 00000000..996c978d --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Globalvar.php @@ -0,0 +1,88 @@ + + * @author Stephan Schmidt + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that enables adding global variables + * from within a template. + * + * Available attributes: + * + * name > name of the variable + * default > default value of the variable + * hidden > whether to output the content of the variable: yes|no + * + * $Id: Globalvar.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Sebastian Mordziol + * @author Stephan Schmidt + */ +class patTemplate_Function_Globalvar extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Globalvar'; + + /** + * reference to the patTemplate object that instantiated the module + * + * @access protected + * @var object + */ + var $_tmpl; + + /** + * set a reference to the patTemplate object that instantiated the reader + * + * @access public + * @param object patTemplate object + */ + function setTemplateReference( &$tmpl ) + { + $this->_tmpl = &$tmpl; + } + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + if( isset( $params['default'] ) ) + { + $this->_tmpl->addGlobalVar( $params['name'], $params['default'] ); + } + + if( !isset( $params['hidden'] ) ) + { + $params['hidden'] = 'no'; + } + + if( $params['hidden'] != 'yes' ) + return $this->_tmpl->getOption('startTag').strtoupper($params['name']).$this->_tmpl->getOption('endTag'); + + return ''; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Highlight.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Highlight.php new file mode 100644 index 00000000..f5b2d1e5 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Highlight.php @@ -0,0 +1,64 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that highlights PHP code in your templates + * + * $Id: Highlight.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Highlight extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Highlight'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call($params, $content) + { + if (!include_once 'Text/Highlighter.php') { + return false; + } + include_once 'Text/Highlighter/Renderer/Html.php'; + + if (!isset($params['type'])) { + return $content; + } + $type = $params['type']; + unset($params['type']); + + if (isset($params['numbers']) && defined($params['numbers'])) { + $params['numbers'] = constant($params['numbers']); + } + + $renderer = &new Text_Highlighter_Renderer_HTML($params); + $highlighter = &Text_Highlighter::factory($type); + $highlighter->setRenderer($renderer); + return $highlighter->highlight(trim($content)); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Img.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Img.php new file mode 100644 index 00000000..88cbaf77 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Img.php @@ -0,0 +1,73 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that returns a complete imagetag and + * width and height are extracted from the image + * + * $Id: Img.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Jens Strobel + */ +class patTemplate_Function_Img extends patTemplate_Function { + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Img'; + + /** + * defaults for some tags + * + * @access protected + * @var array + */ + var $_defaults = array(); + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call ($params, $content) + { + $src= $params['src'] ? $params['src'] : $content; + list($width, $height, $type, $attr)= getimagesize($src); + + $this->_defaults= array( + 'border' => 0, + 'title' => '', + 'alt' => '', + 'width' => $width, + 'height' => $height + ); + + $params = array_merge($this->_defaults, $params); + $tags= ''; + foreach ($params as $key => $value){ + $tags.= sprintf('%s="%s" ', $key, htmlentities($value)); + } + $imgstr= sprintf('', $tags); + + return $imgstr; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Phphighlight.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Phphighlight.php new file mode 100644 index 00000000..14ec8cfb --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Phphighlight.php @@ -0,0 +1,50 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that highlights PHP code in your templates + * + * $Id: Phphighlight.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Phphighlight extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Phphighlight'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + ob_start(); + highlight_string( $content ); + $content = ob_get_contents(); + ob_end_clean(); + return $content; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Strip.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Strip.php new file mode 100644 index 00000000..c01e99b5 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Strip.php @@ -0,0 +1,48 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that strips whitespace from a text + * block. This is an implementation of Smarty's strip function. + * + * $Id: Strip.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Strip extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Strip'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + return preg_replace( '/\s+/m', ' ', $content ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Time.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Time.php new file mode 100644 index 00000000..ddc7d260 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/Time.php @@ -0,0 +1,63 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate function that calculates the current time + * or any other time and returns it in the specified format. + * + * $Id: Time.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Functions + * @author Stephan Schmidt + */ +class patTemplate_Function_Time extends patTemplate_Function +{ + /** + * name of the function + * @access private + * @var string + */ + var $_name = 'Time'; + + /** + * call the function + * + * @access public + * @param array parameters of the function (= attributes of the tag) + * @param string content of the tag + * @return string content to insert into the template + */ + function call( $params, $content ) + { + if( !empty( $content ) ) + { + $params['time'] = $content; + } + + if( isset( $params['time'] ) ) + { + $params['time'] = strtotime( $params['time'] ); + } + else + { + $params['time'] = time(); + } + + + return date( $params['format'], $params['time'] ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Function/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter.php new file mode 100644 index 00000000..55175040 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter.php @@ -0,0 +1,45 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate input filter + * + * $Id: InputFilter.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * An input filter is used to modify the stream + * before it has been processed by patTemplate_Reader. + * + * @abstract + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_InputFilter extends patTemplate_Module +{ + /** + * apply the filter + * + * @access public + * @param string data + * @return string filtered data + */ + function apply( $data ) + { + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/ShortModifiers.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/ShortModifiers.php new file mode 100644 index 00000000..848bdde7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/ShortModifiers.php @@ -0,0 +1,124 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate input filter to allow the short modifier syntax + * that is used by Smarty + * + * $Id: ShortModifiers.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * This will replace the variables with patTemplate:var/> tags that + * have the name and the modifier attribute set. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_InputFilter_ShortModifiers extends patTemplate_InputFilter +{ + /** + * filter name + * + * @access private + * @var string + */ + var $_name = 'ShortModifiers'; + + /** + * parameters of the filter + * + * @access private + * @var array + */ + var $_params = array( + 'copyVars' => true + ); + + /** + * namespace + * + * @access private + * @var string + */ + var $_ns = null; + + /** + * reference to the patTemplate object + * + * @var object patTemplate + * @access private + */ + var $_tmpl = null; + + /** + * set the template reference + * + * @access public + * @param object patTemplate + */ + function setTemplateReference(&$tmpl) + { + $this->_tmpl = &$tmpl; + } + + /** + * generate the tag + * + * @access public + * @param array matches from preg_replace + * @return string tag + */ + function _generateReplace($matches) + { + if ($this->getParam('copyVars') === true) { + $newName = $matches[2] . '_' . $matches[3]; + if (isset( $matches[4] )) { + $newName .= $matches[4]; + } + $replace = $matches[1] . '<' . $this->_ns . ':var copyFrom="' . $matches[2] . '" name="' . $newName . '" modifier="' . $matches[3] . '"'; + } else { + $replace = $matches[1] . '<' . $this->_ns . ':var name="' . $matches[2] . '" modifier="' . $matches[3] . '"'; + } + + $n = count($matches) - 1; + for ($i = 4; $i < $n; $i++ ) { + $replace .= ' ' . $matches[++$i] . '="' . $matches[++$i] . '"'; + } + $replace .= '/>'; + return $replace; + } + + /** + * replace the variables + * + * @access public + * @param string data + * @return string data with variables replaced + */ + function apply($data) + { + $startTag = $this->_tmpl->getStartTag(); + $endTag = $this->_tmpl->getEndTag(); + + $this->_ns = $this->_tmpl->getNamespace(); + if (is_array($this->_ns)) { + $this->_ns = array_shift($this->_ns); + } + $regex = chr( 1 ) . "([^\\\])" . $startTag . "([^a-z]+)\|(.+[^\\\])(\|(.+):(.+[^\\\]))*" . $endTag . chr( 1 ) . "U"; + $data = preg_replace_callback($regex, array( $this, '_generateReplace' ), $data); + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/StripComments.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/StripComments.php new file mode 100644 index 00000000..8966c080 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/StripComments.php @@ -0,0 +1,54 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate StripComments output filter + * + * $Id: StripComments.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Will remove all HTML comments. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_InputFilter_StripComments extends patTemplate_InputFilter +{ + /** + * filter name + * + * @access protected + * @abstract + * @var string + */ + var $_name = 'StripComments'; + + /** + * compress the data + * + * @access public + * @param string data + * @return string data without whitespace + */ + function apply( $data ) + { + $data = preg_replace( '��msU', '', $data ); + $data = preg_replace( '�/\*.*\*/�msU', '', $data ); + + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/InputFilter/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier.php new file mode 100644 index 00000000..0a38da3c --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier.php @@ -0,0 +1,45 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate variable modifiers + * + * $Id: Modifier.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * A modifier is used to modify a variable when it's parsed + * into the template. + * + * @abstract + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + */ +class patTemplate_Modifier extends patTemplate_Module +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + return $value; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Dateformat.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Dateformat.php new file mode 100644 index 00000000..915c869c --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Dateformat.php @@ -0,0 +1,53 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modfifier Dateformat + * + * formats dates and times according to a format string. + * + * Possible attributes are: + * - format (string) + * + * See the PHP documentation for strftime() for + * more information. + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + * @link http://www.php.net/manual/en/function.strftime.php + */ +class patTemplate_Modifier_Dateformat extends patTemplate_Modifier +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + if (!isset($params['format'])) { + return $value; + } + + if (!preg_match('/^[0-9]+$/', $value)) { + $value = strtotime($value); + } + + return strftime($params['format'], $value); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Expression.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Expression.php new file mode 100644 index 00000000..b4a40847 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Expression.php @@ -0,0 +1,63 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modfifier Expression + * + * Evaluates an expression and returns one of + * the defined values for true and false. + * + * Possible attributes are: + * - expression (string) + * - true (string) + * - false (string) + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + * @link http://www.php.net/manual/en/function.wordwrap.php + */ +class patTemplate_Modifier_Expression extends patTemplate_Modifier +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + /* + * true and false + */ + if( !isset( $params['true'] ) ) + $params['true'] = 'true'; + if( !isset( $params['false'] ) ) + $params['false']= 'false'; + + /* + * replace the value in the expression + */ + $params['expression'] = str_replace( '$self', "'$value'", $params['expression'] ); + + @eval( '$result = '.$params['expression'].';' ); + + if ($result === true) { + return str_replace( '$self', $value, $params['true'] ); + } + return str_replace( '$self', $value, $params['false'] ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/Img.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/Img.php new file mode 100644 index 00000000..158b11a3 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/Img.php @@ -0,0 +1,63 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Modifier that creates an HTML image tag from a variable + * + * It automatically retrieves the width and height of the image. + * + * $Id: Img.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + */ +class patTemplate_Modifier_HTML_Img extends patTemplate_Modifier +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + $size = getimagesize( $value ); + $params['src'] = $value; + $params['width'] = $size[0]; + $params['height'] = $size[1]; + return 'arrayToAttributes($params).' />'; + } + + /** + * create an attribute list + * + * @access private + * @param array + * @return string + */ + function arrayToAttributes( $array ) + { + $string = ''; + foreach( $array as $key => $val ) + { + $string .= ' '.$key.'="'.htmlspecialchars( $val ).'"'; + } + return $string; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/HTML/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Numberformat.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Numberformat.php new file mode 100644 index 00000000..862816d5 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Numberformat.php @@ -0,0 +1,53 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modfifier Numberformat + * + * formats dates and times according to a format string. + * + * Possible attributes are: + * - decimals (int) + * - point + * - separator + * + * See the PHP documentation for number_format() for + * more information. + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + * @link http://www.php.net/manual/en/function.strftime.php + */ +class patTemplate_Modifier_Numberformat extends patTemplate_Modifier +{ + var $defaults = array( + 'decimals' => 2, + 'point' => '.', + 'separator' => ',' + ); + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify($value, $params = array()) + { + $params = array_merge($this->defaults, $params); + return @number_format($value, $params['decimals'], $params['point'], $params['separator']); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/QuoteLatex.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/QuoteLatex.php new file mode 100644 index 00000000..8abcb8ea --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/QuoteLatex.php @@ -0,0 +1,50 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modifier that quotes LaTeX special chars + * + * This is useful when creating PDF documents with patTemplate + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + * @link http://www.php.net/manual/en/function.strftime.php + */ +class patTemplate_Modifier_QuoteLatex extends patTemplate_Modifier +{ + /** + * + * + */ + var $_chars = array( + '%' => '\%', + '&' => '\&', + '_' => '\_', + '$' => '\$' + ); + + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + return strtr($value, $this->_chars); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Surround.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Surround.php new file mode 100644 index 00000000..4b69d87b --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Surround.php @@ -0,0 +1,90 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modfifier Surround + * + * splits a text and surrunds each part by custom start and end strings + * + * Possible attributes are: + * - delimiter (string) + * - start (string) + * - end (string) + * - keepdelimiter (yes|no) default: no + * - withfirst (yes|no) default: yes + * - withlast (yes|no) default: yes + * + * + * @package patTemplate + * @subpackage Modifiers + * @author gERD Schaufelberger + * @version 0.2 + */ +class patTemplate_Modifier_Surround extends patTemplate_Modifier +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + // set default values + $delimiter = "\n"; + $start = ''; + $end = ''; + + // where to split + if( isset( $params['delimiter'] ) ) + { + $delimiter = $params['delimiter']; + } + + if( isset( $params['start'] ) ) + { + $start = $params['start']; + } + + if( isset( $params['end'] ) ) + { + $end = $params['end']; + } + + // append the delimiter? + if( isset( $params['keepdelimiter'] ) && $params['keepdelimiter'] === 'yes' ) + { + $end .= $delimiter; + } + + $split = explode( $delimiter, $value ); + $value = implode( $end . $start, $split ); + + // add first? + if( !isset( $params['withfirst'] ) || $params['withfirst'] !== 'no' ) + { + $value = $start . $value; + } + + // add last? + if( !isset( $params['withlast'] ) || $params['withlast'] !== 'no' ) + { + $value .= $end; + } + + return $value; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Truncate.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Truncate.php new file mode 100644 index 00000000..e96bce9f --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Truncate.php @@ -0,0 +1,88 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modifier Truncate + * + * Truncate a string variable to fixed length and add a suffix if it was truncated. + * It can also start from an offset and add a prefix. + * + * Possible attributes are: + * - length (integer) + * - suffix (string) + * - start + * - prefix (string) + * + * @package patTemplate + * @subpackage Modifiers + * @author Rafa Couto + */ +class patTemplate_Modifier_Truncate extends patTemplate_Modifier +{ + + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify($value, $params = array()) + { + // length + if (!isset( $params['length'])) { + return $value; + } + settype($params['length'], 'integer'); + + $decode = isset( $params['htmlsafe'] ); + if (function_exists( 'html_entity_decode' ) && $decode) { + $value = html_entity_decode( $value ); + } + + // start + if (isset($params['start'])) { + settype( $params['start'], 'integer' ); + } else { + $params['start'] = 0; + } + + // prefix + if (isset($params['prefix'])) { + $prefix = ($params['start'] == 0 ? '' : $params['prefix']); + } else { + $prefix = ''; + } + + // suffix + if (isset($params['suffix'])) { + $suffix = $params['suffix']; + } else { + $suffix = ''; + } + + $initial_len = strlen($value); + $value = substr($value, $params['start'], $params['length']); + + if ($initial_len <= strlen($value)) { + $suffix = ''; + } + + $value = $prefix.$value.$suffix; + + return $decode ? htmlspecialchars( $value, ENT_QUOTES ) : $value; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Wordwrapper.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Wordwrapper.php new file mode 100644 index 00000000..41f2d4c0 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/Wordwrapper.php @@ -0,0 +1,74 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate modfifier Wordwrapper + * + * Wraps lines of long texts. + * + * Possible attributes are: + * - width (integer) + * - break (string) + * - cut (yes|no) + * - nl2br (yes|no) + * + * See the PHP documentation for wordwrap() for + * more information. + * + * @package patTemplate + * @subpackage Modifiers + * @author Stephan Schmidt + * @link http://www.php.net/manual/en/function.wordwrap.php + */ +class patTemplate_Modifier_Wordwrapper extends patTemplate_Modifier +{ + /** + * modify the value + * + * @access public + * @param string value + * @return string modified value + */ + function modify( $value, $params = array() ) + { + /** + * width + */ + if( !isset( $params['width'] ) ) + $params['width'] = 72; + settype( $params['width'], 'integer' ); + + /** + * character used for linebreaks + */ + if( !isset( $params['break'] ) ) + $params['break'] = "\n"; + + /** + * cut at the specified width + */ + if( !isset( $params['cut'] ) ) + $params['cut'] = 'no'; + + $params['cut'] = ($params['cut'] === 'yes') ? true : false; + + $value = wordwrap( $value, $params['width'], $params['break'], $params['cut'] ); + + if( isset( $params['nl2br'] ) && $params['nl2br'] === 'yes' ) + $value = nl2br( $value ); + + return $value; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Modifier/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Module.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Module.php new file mode 100644 index 00000000..300d8cb7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Module.php @@ -0,0 +1,87 @@ + + */ + +/** + * patTemplate Module base class + * + * $Id: Module.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * The patTemplate_Module is the base class for all patTemplate + * modules like readers, dumpers, filters, etc. + * + * @abstract + * @package patTemplate + * @author Stephan Schmidt + * @abstract + */ +class patTemplate_Module +{ + /** + * module name + * + * This has to be set in the final + * module classes. + * + * @access protected + * @var string + */ + var $_name = null; + + /** + * module parameters + * + * @access protected + * @var array + */ + var $_params = array(); + + /** + * get the name of the module + * + * @access public + * @return string name of the module + */ + function getName() + { + return $this->_name; + } + + /** + * sets parameters of the module + * + * @access public + * @param array assoc array containing parameters + * @param boolean flag to indicate, whether previously set parameters should be cleared + */ + function setParams( $params, $clear = false ) + { + if( $clear === true ) + $this->_params = array(); + $this->_params = array_merge( $this->_params, $params ); + } + + /** + * gets a parameter of the module + * + * @access public + * @param string name of the parameter + * @return mixed value of the parameter + */ + function getParam( $name ) + { + if( isset( $this->_params[$name] ) ) + return $this->_params[$name]; + return false; + } +} +?> \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputCache.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputCache.php new file mode 100644 index 00000000..ea20aeb5 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputCache.php @@ -0,0 +1,44 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate output cache + * + * $Id: OutputCache.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * An output cache is used to cache the data before + * the template has been read. + * + * It stores the HTML (or any other output) that is + * generated to increase performance. + * + * This is not related to a template cache! + * + * @abstract + * @package patTemplate + * @subpackage Caches + * @author Stephan Schmidt + */ +class patTemplate_OutputCache extends patTemplate_Module +{ +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter.php new file mode 100644 index 00000000..e9fb9852 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter.php @@ -0,0 +1,47 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate output filter + * + * $Id: OutputFilter.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * An output filter is used to modify the output + * after it has been processed by patTemplate, but before + * it is sent to the browser. + * + * @abstract + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter extends patTemplate_Module +{ + /** + * apply the filter + * + * @access public + * @param string data + * @return string filtered data + */ + function apply( $data ) + { + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/BBCode.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/BBCode.php new file mode 100644 index 00000000..06bef208 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/BBCode.php @@ -0,0 +1,118 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate BBCode output filter + * + * $Id: BBCode.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Uses patBBCode. Note that patBBCode's syntax is not + * entirely the same than the 'official' BBCode. See the + * patBBCode projet page for details. + * + * The following parameters are available: + * + * - skinDir (required) + * The folder where BBCode templates are stored + * + * - reader (required) + * The type of reader to use + * + * - BBCode (optional) + * A fully configured BBCode objet to use. The other + * two options are not required if you set this. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + * @author Sebastian Mordziol + * @link http://www.php-tools.net/site.php?file=patBBCode/Overview.xml + */ +class patTemplate_OutputFilter_BBCode extends patTemplate_OutputFilter +{ + /** + * filter name + * + * @access protected + * @abstract + * @var string + */ + var $_name = 'BBCode'; + + /** + * BBCode parser + * + * @access private + * @var object patBBCode + */ + var $BBCode = null; + + /** + * remove all whitespace from the output + * + * @access public + * @param string data + * @return string data without whitespace + */ + function apply( $data ) + { + if( !$this->_prepare() ) + return $data; + + $data = $this->BBCode->parseString( $data ); + + return $data; + } + + /** + * prepare BBCode object + * + * @access private + */ + function _prepare() + { + // there already is a BBCode object + if( is_object( $this->BBCode ) ) { + return true; + } + + // maybe a fully configured BBCode object was provided? + if( isset( $this->_params['BBCode'] ) ) { + $this->BBCode =& $this->_params['BBCode']; + return true; + } + + // include the patBBCode class + if( !class_exists( 'patBBCode' ) ) + { + if( !@include_once 'pat/patBBCode.php' ) + return false; + } + + $this->BBCode = &new patBBCode(); + + if( isset( $this->_params['skinDir'] ) ) + $this->BBCode->setSkinDir( $this->_params['skinDir'] ); + + $reader =& $this->BBCode->createConfigReader( $this->_params['reader'] ); + + // give patBBCode the reader we just created + $this->BBCode->setConfigReader( $reader ); + + return true; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Gzip.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Gzip.php new file mode 100644 index 00000000..c1042194 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Gzip.php @@ -0,0 +1,106 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate GZip output filter + * + * $Id: Gzip.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Checks the accept encoding of the browser and + * compresses the data before sending it to the client. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter_Gzip extends patTemplate_OutputFilter +{ + /** + * filter name + * + * This has to be set in the final + * filter classes. + * + * @access protected + * @abstract + * @var string + */ + var $_name = 'Gzip'; + + /** + * compress the data + * + * @access public + * @param string data + * @return string compressed data + */ + function apply( $data ) + { + if (!$this->_clientSupportsGzip()) { + return $data; + } + + $size = strlen( $data ); + $crc = crc32( $data ); + + $data = gzcompress( $data, 9 ); + $data = substr( $data, 0, strlen( $data ) - 4 ); + + $data .= $this->_gfc( $crc ); + $data .= $this->_gfc( $size ); + + header( 'Content-Encoding: gzip' ); + $data = "\x1f\x8b\x08\x00\x00\x00\x00\x00" . $data; + return $data; + } + + /** + * check, whether client supports compressed data + * + * @access private + * @return boolean + */ + function _clientSupportsGzip() + { + if (!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { + return false; + } + + if (false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) { + return true; + } + return false; + } + + /** + * get value as hex-string + * + * @access public + * @param integer $value value to convert + * @return string $string converted string + */ + function _gfc( $value ) + { + $str = ''; + for ($i = 0; $i < 4; $i ++) { + $str .= chr( $value % 256 ); + $value = floor( $value / 256 ); + } + return $str; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/HighlightPhp.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/HighlightPhp.php new file mode 100644 index 00000000..89e95e82 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/HighlightPhp.php @@ -0,0 +1,51 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate HighlightPHP filter + * + * $Id: HighlightPhp.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Highlights PHP code in the output. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter_HighlightPhp extends patTemplate_OutputFilter +{ + /** + * filter name + * + * @access protected + * @abstract + * @var string + */ + var $_name = 'HighlightPhp'; + + /** + * remove all whitespace from the output + * + * @access public + * @param string data + * @return string data without whitespace + */ + function apply( $data ) + { + return highlight_string($data, true); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/PdfLatex.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/PdfLatex.php new file mode 100644 index 00000000..02841a52 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/PdfLatex.php @@ -0,0 +1,65 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate output filter that creates PDF files from latex + * + * $Id: PdfLatex.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter_PdfLatex extends patTemplate_OutputFilter +{ + /** + * filter name + * + * This has to be set in the final + * filter classes. + * + * @var string + */ + var $_name = 'PdfLatex'; + + var $_params = array( + 'cacheFolder' => './' + ); + + /** + * tidy the data + * + * @access public + * @param string data + * @return string compressed data + */ + function apply( $data ) + { + $cacheFolder = $this->getParam('cacheFolder'); + $texFile = tempnam($cacheFolder, 'pt_tex_'); + $fp = fopen($texFile, 'w'); + fwrite($fp, $data); + fclose($fp); + + $command = 'pdflatex '.$texFile; + exec($command); + exec($command); + + $pdf = $texFile . '.pdf'; + $pdf = file_get_contents($pdf); + + return $pdf; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/StripWhitespace.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/StripWhitespace.php new file mode 100644 index 00000000..2abd2979 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/StripWhitespace.php @@ -0,0 +1,54 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate StripWhitespace output filter + * + * $Id: StripWhitespace.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Will remove all whitespace and replace it with a single space. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter_StripWhitespace extends patTemplate_OutputFilter +{ + /** + * filter name + * + * @access protected + * @abstract + * @var string + */ + var $_name = 'StripWhitespace'; + + /** + * remove all whitespace from the output + * + * @access public + * @param string data + * @return string data without whitespace + */ + function apply( $data ) + { + $data = str_replace( "\n", ' ', $data ); + $data = preg_replace( '/\s\s+/', ' ', $data ); + + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Tidy.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Tidy.php new file mode 100644 index 00000000..678a79d4 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/Tidy.php @@ -0,0 +1,79 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * requires tidy extension + */ +define( 'PATTEMPLATE_OUTPUTFILTER_TIDY_ERROR_NOT_SUPPORTED', 'patTemplate::Outputfilter::Tidy::1' ); + +/** + * patTemplate Tidy output filter + * + * $Id: Tidy.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Used to tidy up your resulting HTML document, + * requires ext/tidy. + * + * @package patTemplate + * @subpackage Filters + * @author Stephan Schmidt + */ +class patTemplate_OutputFilter_Tidy extends patTemplate_OutputFilter +{ + /*** + * filter name + * + * This has to be set in the final + * filter classes. + * + * @var string + */ + var $_name = 'Tidy'; + + /*** + * tidy the data + * + * @access public + * @param string data + * @return string compressed data + */ + function apply( $data ) + { + if (!function_exists('tidy_parse_string')) { + return $data; + } + + /** + * tidy 1.0 + */ + if (function_exists( 'tidy_setopt' ) && is_array( $this->_params)) { + foreach ($this->_params as $opt => $value) { + tidy_setopt( $opt, $value ); + } + tidy_parse_string($data); + tidy_clean_repair(); + $data = tidy_get_output(); + } else { + $tidy = tidy_parse_string($data, $this->_params); + tidy_clean_repair($tidy); + $data = tidy_get_output($tidy); + } + + return $data; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/OutputFilter/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader.php new file mode 100644 index 00000000..3463d589 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader.php @@ -0,0 +1,1573 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * No input + */ +define( 'PATTEMPLATE_READER_ERROR_NO_INPUT', 6000 ); + +/** + * Unknown tag + */ +define( 'PATTEMPLATE_READER_ERROR_UNKNOWN_TAG', 6001 ); + +/** + * Invalid tag (missing attribute) + */ +define( 'PATTEMPLATE_READER_ERROR_INVALID_TAG', 6002 ); + +/** + * Closing tag is missing + */ +define( 'PATTEMPLATE_READER_ERROR_NO_CLOSING_TAG', 6003 ); + +/** + * Invalid closing tag + */ +define( 'PATTEMPLATE_READER_ERROR_INVALID_CLOSING_TAG', 6004 ); + +/** + * Invalid condition specified + */ +define( 'PATTEMPLATE_READER_ERROR_INVALID_CONDITION', 6005 ); + +/** + * No name has been specified + */ +define( 'PATTEMPLATE_READER_ERROR_NO_NAME_SPECIFIED', 6010 ); + +/** + * CData in a conditional template + */ +define( 'PATTEMPLATE_READER_NOTICE_INVALID_CDATA_SECTION', 6050 ); + +/** + * template already exists + */ +define( 'PATTEMPLATE_READER_NOTICE_TEMPLATE_EXISTS', 6051 ); + +/** + * Base class for patTemplate readers + * + * This class is able to parse patTemplate tags from any string you hand it over + * It will emulate some kind of SAX parsing by calling start-, end- and CData-handlers. + * + * @abstract + * @package patTemplate + * @subpackage Readers + * @author Stephan Schmidt + */ +class patTemplate_Reader extends patTemplate_Module +{ + /** + * reference to the patTemplate object that instantiated the module + * + * @access protected + * @var object + */ + var $_tmpl; + + /** + * stack for all open elements + * @access private + * @var array + */ + var $_elStack; + + /** + * stack for all open templates + * @access private + * @var array + */ + var $_tmplStack; + + /** + * character data + * @access private + * @var array + */ + var $_data; + + /** + * tag depth + * @access private + * @var integer + */ + var $_depth; + + /** + * templates that have been found + * @access protected + * @var array + */ + var $_templates = array(); + + /** + * path to the template + * @access protected + * @var array + */ + var $_path = array(); + + /** + * start tag for variables + * @access private + * @var string + */ + var $_startTag; + + /** + * end tag for variables + * @access private + * @var string + */ + var $_endTag; + + /** + * default attributes + * + * @access private + * @var array + */ + var $_defaultAtts = array(); + + /** + * root attributes + * + * This is used when reading the template content + * from an external file. + * + * @access private + * @var array + */ + var $_rootAtts = array(); + + /** + * inherit attributes + * + * @access private + * @var array + */ + var $_inheritAtts = array(); + + /** + * name of the first template that has been found + * + * @access private + * @var string + */ + var $_root = null; + + /** + * all data that has been processed + * + * @access private + * @var string + */ + var $_processedData = null; + + /** + * current input + * + * @access private + * @var string + */ + var $_currentInput = null; + + /** + * all loaded functions + * + * @access private + * @var array + */ + var $_functions = array(); + + /** + * function aliases + * + * @access private + * @var array + */ + var $_funcAliases = array(); + + /** + * options + * + * @access private + * @var array + */ + var $_options = array(); + + /** + * reader is in use + * + * @access private + * @var boolean + */ + var $_inUse = false; + + /** + * set a reference to the patTemplate object that instantiated the reader + * + * @access public + * @param object patTemplate object + */ + function setTemplateReference( &$tmpl ) + { + $this->_tmpl = &$tmpl; + } + + /** + * read templates from any input + * + * @abstract must be implemented in the template readers + * @param mixed input to read from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @param array options, not implemented in current versions, but future versions will allow passing of options + * @return array template structure + */ + function readTemplates( $input, $options = array() ) + { + return array(); + } + + /** + * load template from any input + * + * If the a template is loaded, the content will not get + * analyzed but the whole content is returned as a string. + * + * @abstract must be implemented in the template readers + * @param mixed input to load from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @param array options, not implemented in current versions, but future versions will allow passing of options + * @return string template content + */ + function loadTemplate( $input, $options = array() ) + { + return $input; + } + + /** + * set options + * + * @access public + * @param array array containing options + */ + function setOptions( $options ) + { + $this->_startTag = $options['startTag']; + $this->_endTag = $options['endTag']; + + $this->_options = $options; + + if (isset($options['functionAliases'])) { + $this->_funcAliases = $options['functionAliases']; + } + array_map('strtolower', $this->_funcAliases); + } + + /** + * add an alias for a function + * + * @access public + * @param string alias + * @param string function name + */ + function addFunctionAlias($alias, $function) + { + $this->_funcAliases[strtolower($alias)] = $function; + } + + /** + * set the root attributes + * + * @access public + * @param array array containing options + */ + function setRootAttributes( $attributes ) + { + $this->_rootAtts = $attributes; + } + + /** + * parse templates from string + * + * @access private + * @param string string to parse + * @return array templates + */ + function parseString( $string ) + { + $this->_inUse = true; + + /** + * apply input filter before parsing + */ + $string = $this->_tmpl->applyInputFilters( $string ); + + $this->_inheritAtts = array(); + $this->_elStack = array(); + $this->_data = array( '' ); + $this->_tmplStack = array(); + $this->_depth = 0; + $this->_templates = array(); + $this->_path = array(); + $this->_processedData = ''; + + $this->_defaultAtts = $this->_tmpl->getDefaultAttributes(); + + if( !isset( $this->_defaultAtts['autoload'] ) ) { + $this->_defaultAtts['autoload'] = 'on'; + } + + /** + * create a special root template + */ + $attributes = $this->_rootAtts; + $attributes['name'] = '__ptroot'; + + $rootTemplate = $this->_initTemplate( $attributes ); + $this->_root = null; + unset( $rootTemplate['isRoot'] ); + + array_push( $this->_tmplStack, $rootTemplate ); + + $patNamespace = $this->_tmpl->getNamespace(); + if (is_array($patNamespace)) { + $patNamespace = array_map('strtolower', $patNamespace); + } else { + $patNamespace = array(strtolower( $patNamespace )); + } + + /** + *start parsing + */ + $regexp = '/(<(\/?)([[:alnum:]]+):([[:alnum:]]+)[[:space:]]*([^>]*)>)/im'; + + $tokens = preg_split( $regexp, $string, -1, PREG_SPLIT_DELIM_CAPTURE ); + + /** + * the first token is always character data + * Though it could just be empty + */ + if( $tokens[0] != '' ) { + $this->_characterData( $tokens[0] ); + } + + $cnt = count( $tokens ); + $i = 1; + // process all tokens + while( $i < $cnt ) { + $fullTag = $tokens[$i++]; + $closing = $tokens[$i++]; + $namespace = strtolower( $tokens[$i++] ); + $tagname = strtolower( $tokens[$i++] ); + $attString = $tokens[$i++]; + $empty = substr( $attString, -1 ); + $data = $tokens[$i++]; + + /** + * check, whether it's a known namespace + * currently only the template namespace is possible + */ + if( !in_array($namespace, $patNamespace) ) { + $this->_characterData( $fullTag ); + $this->_characterData( $data ); + continue; + } + + /** + * is it a closing tag? + */ + if( $closing === '/' ) { + $result = $this->_endElement( $namespace, $tagname ); + if( patErrorManager::isError( $result ) ) { + return $result; + } + $this->_characterData( $data ); + continue; + } + + /** + * Is empty or opening tag! + */ + if( $empty === '/' ) { + $attString = substr( $attString, 0, -1 ); + } + + $attributes = $this->_parseAttributes( $attString ); + $result = $this->_startElement( $namespace, $tagname, $attributes ); + if( patErrorManager::isError( $result ) ) { + return $result; + } + + /** + * check, if the tag is empty + */ + if( $empty === '/' ) { + $result = $this->_endElement( $namespace, $tagname ); + if( patErrorManager::isError( $result ) ) { + return $result; + } + } + + $this->_characterData( $data ); + } + + $rootTemplate = array_pop( $this->_tmplStack ); + + $this->_closeTemplate( $rootTemplate, $this->_data[0] ); + + /** + * check for tags that are still open + */ + if( $this->_depth > 0 ) { + $el = array_pop( $this->_elStack ); + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_CLOSING_TAG, + $this->_createErrorMessage( "No closing tag for {$el['ns']}:{$el['name']} found" ) + ); + } + + $this->_inUse = false; + + return $this->_templates; + } + + /** + * parse an attribute string and build an array + * + * @access private + * @param string attribute string + * @param array attribute array + */ + function _parseAttributes( $string ) + { + static $entities = array( + '<' => '<', + '>' => '>', + '&' => '&', + '"' => '"', + ''' => '\'' + ); + + $attributes = array(); + $match = array(); + preg_match_all('/([a-zA-Z_0-9]+)="((?:\\\.|[^"\\\])*)"/U', $string, $match); + for ($i = 0; $i < count($match[1]); $i++) { + $attributes[strtolower( $match[1][$i] )] = strtr( (string)$match[2][$i], $entities ); + } + return $attributes; + } + + /** + * handle start element + * + * @access private + * @param string element name + * @param array attributes + */ + function _startElement( $ns, $name, $attributes ) + { + array_push( $this->_elStack, array( + 'ns' => $ns, + 'name' => $name, + 'attributes' => $attributes, + ) + ); + + $this->_depth++; + + $this->_data[$this->_depth] = ''; + + /** + * handle tag + */ + switch( $name ) + { + /** + * template + */ + case 'tmpl': + $result = $this->_initTemplate( $attributes ); + break; + + /** + * sub-template + */ + case 'sub': + $result = $this->_initSubTemplate( $attributes ); + break; + + /** + * link + */ + case 'link': + $result = $this->_initLink( $attributes ); + break; + + /** + * variable + */ + case 'var': + $result = false; + break; + + /** + * instance + */ + case 'instance': + case 'comment': + $result = false; + break; + + /** + * any other tag + */ + default: + if (isset($this->_funcAliases[strtolower($name)])) { + $name = $this->_funcAliases[strtolower($name)]; + } + $name = ucfirst( $name ); + + if( !$this->_tmpl->moduleExists( 'Function', $name ) ) { + + if (isset($this->_options['defaultFunction']) && !empty($this->_options['defaultFunction'])) { + $attributes['_originalTag'] = $name; + $name = ucfirst($this->_options['defaultFunction']); + } else { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_UNKNOWN_TAG, + $this->_createErrorMessage( "Unknown tag {$ns}:{$name}." ) + ); + } + } + $result = array( + 'type' => 'custom', + 'function' => $name, + 'attributes' => $attributes + ); + break; + } + + if( patErrorManager::isError( $result ) ) { + return $result; + } + + array_push( $this->_tmplStack, $result ); + return true; + } + + /** + * handle end element + * + * @access private + * @param string element name + */ + function _endElement( $ns, $name ) + { + $el = array_pop( $this->_elStack ); + $data = $this->_getCData(); + $this->_depth--; + + if( $el['name'] != $name || $el['ns'] != $ns ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_CLOSING_TAG, + $this->_createErrorMessage( "Invalid closing tag {$ns}:{$name}, {$el['ns']}:{$el['name']} expected" ) + ); + } + + $tmpl = array_pop( $this->_tmplStack ); + + /** + * handle tag + */ + switch( $name ) + { + /** + * template + */ + case 'tmpl': + $this->_closeTemplate( $tmpl, $data ); + break; + + /** + * sub-template + */ + case 'sub': + $this->_closeSubTemplate( $tmpl, $data ); + break; + + /** + * link + */ + case 'link': + $this->_closeLink( $tmpl ); + break; + + /** + * variable + */ + case 'var': + $this->_handleVariable( $el['attributes'], $data ); + break; + + /** + * instance + */ + case 'instance': + break; + + /** + * comment + */ + case 'comment': + $this->_handleComment( $el['attributes'], $data ); + break; + + /** + * custom function + */ + default: + $name = ucfirst( $tmpl['function'] ); + + if( !isset( $this->_functions[$name] ) ) { + $this->_functions[$name] = $this->_tmpl->loadModule( 'Function', $name ); + $this->_functions[$name]->setReader( $this ); + } + + $result = $this->_functions[$name]->call( $tmpl['attributes'], $data ); + + if( patErrorManager::isError( $result ) ) { + return $result; + } + + if( is_string( $result ) ) { + $this->_characterData( $result, false ); + } + break; + } + return true; + } + + /** + * handle character data + * + * @access private + * @param string data + */ + function _characterData( $data, $readFromTemplate = true ) + { + $this->_data[$this->_depth] .= $data; + + if ($readFromTemplate) { + $this->_processedData .= $data; + } + + return true; + } + + /** + * handle a Link + * + * @access private + * @param array attributes + * @return boolean true on success + */ + function _initLink( $attributes ) + { + /** + * needs a src attribute + */ + if( !isset( $attributes['src'] ) ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( "Attribute 'src' missing for link" ) + ); + } + + /** + * create a new template + */ + $tmpl = array( + 'type' => 'link', + 'src' => $attributes['src'], + ); + return $tmpl; + } + + /** + * close a link template + * + * It will be added to the dependecies of the parent template. + * + * @access private + * @param array template definition for the link + */ + function _closeLink( $tmpl ) + { + /** + * add it to the dependencies + */ + if( !empty( $this->_tmplStack ) ) + { + $this->_addToParentTag( 'dependencies', strtolower( $tmpl['src'] ) ); + $this->_characterData( sprintf( "%sTMPL:%s%s", $this->_startTag, strtoupper( $tmpl['src'] ), $this->_endTag ) ); + } + + return true; + } + + /** + * create a new template + * + * @access private + * @param array attributes + * @return boolean true on success + */ + function _initTemplate( $attributes ) + { + /** + * build name for the template + */ + if (!isset( $attributes['name'] )) { + $name = $this->_buildTemplateName(); + } else { + $name = strtolower( $attributes['name'] ); + unset( $attributes['name'] ); + } + + /** + * name must be unique + */ + if( isset( $this->_templates[$name] ) || $this->_tmpl->exists( $name ) ) { + patErrorManager::raiseNotice( + PATTEMPLATE_READER_NOTICE_TEMPLATE_EXISTS, + $this->_createErrorMessage( "Template $name already exists" ), + $name + ); + } + + /** + * update the path + */ + array_push( $this->_path, $name ); + + if( isset( $attributes['maxloop'] ) ) { + if (!isset( $attributes['parent'] )) { + $attributes['parent'] = $this->_getFromParentTemplate( 'name' ); + } + } + + $attributes = $this->_prepareTmplAttributes( $attributes, $name ); + + array_push( $this->_inheritAtts, array( + 'whitespace' => $attributes['whitespace'], + 'unusedvars' => $attributes['unusedvars'], + 'autoclear' => $attributes['autoclear'] + ) + ); + + /** + * create a new template + */ + $tmpl = array( + 'type' => 'tmpl', + 'name' => $name, + 'attributes' => $attributes, + 'content' => '', + 'dependencies' => array(), + 'varspecs' => array(), + 'comments' => array(), + 'loaded' => false, + 'parsed' => false, + 'input' => $this->_name.'://'.$this->_currentInput + ); + + if( $this->_root == null ) { + $this->_root = $name; + $tmpl['isRoot'] = true; + } + + + /** + * prepare subtemplates + */ + switch( $attributes['type'] ) { + case 'condition': + case 'modulo': + $tmpl['subtemplates'] = array(); + break; + } + + return $tmpl; + } + + /** + * prepare attributes + * + * @access private + * @param array attributes + * @param string template name (only used for error messages) + * @return array attributes + */ + function _prepareTmplAttributes( $attributes, $templatename ) + { + /** + * do not prepare twice + */ + if( isset( $attributes['__prepared'] ) && $attributes['__prepared'] === true ) { + return $attributes; + } + + $attributes = $this->_inheritAttributes( $attributes ); + + /** + * get the attributes + */ + $attributes = array_merge( $this->_tmpl->getDefaultAttributes(), $attributes ); + + $attributes['type'] = strtolower( $attributes['type'] ); + + if( !isset( $attributes['rowoffset'] ) ) { + $attributes['rowoffset'] = 1; + } + + if( !isset( $attributes['addsystemvars'] ) ) { + $attributes['addsystemvars'] = false; + } else { + switch ($attributes['addsystemvars']) { + case 'on': + case 'boolean': + $attributes['addsystemvars'] = 'boolean'; + break; + case 'int': + case 'integer': + $attributes['addsystemvars'] = 'integer'; + break; + case 'off': + $attributes['addsystemvars'] = false; + break; + } + } + + /** + * external template + */ + if( isset( $attributes['src'] ) ) { + if( !isset( $attributes['parse'] ) ) + $attributes['parse'] = 'on'; + if( !isset( $attributes['reader'] ) ) + $attributes['reader'] = $this->getName(); + if( !isset( $attributes['autoload'] ) ) + $attributes['autoload'] = $this->_defaultAtts['autoload']; + + if (isset($attributes['relative']) && strtolower($attributes['relative'] === 'yes')) { + $attributes['relative'] = $this->getCurrentInput(); + } else { + $attributes['relative'] = false; + } + } + + /** + * varscope is set + */ + if( isset( $attributes['varscope'] ) ) { + /** + * varscope is parent + */ + if( $attributes['varscope'] === '__parent' ) { + $attributes['varscope'] = $this->_getFromParentTemplate( 'name' ); + } + + $attributes['varscope'] = strtolower( $attributes['varscope'] ); + if (strstr($attributes['varscope'], ',')) { + $attributes['varscope'] = array_map('trim', explode(',', $attributes['varscope'])); + } + } + + switch( $attributes['type'] ) { + /** + * validate condition template + */ + case 'condition': + if( !isset( $attributes['conditionvar'] ) ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( "Attribute 'conditionvar' missing for $templatename" ) + ); + } + $attributes['conditionvar'] = strtoupper( $attributes['conditionvar'] ); + + if( strstr( $attributes['conditionvar'], '.' ) ) { + list( $attributes['conditiontmpl'], $attributes['conditionvar'] ) = explode( '.', $attributes['conditionvar'] ); + $attributes['conditiontmpl'] = strtolower( $attributes['conditiontmpl'] ); + } + + $attributes['autoclear'] = 'yes'; + + if (!isset( $attributes['useglobals'] )) { + $attributes['useglobals'] = 'no'; + } + break; + + /** + * validate simplecondition template + */ + case 'simplecondition': + if( !isset( $attributes['requiredvars'] ) ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( "Attribute 'requiredvars' missing for $templatename" ) + ); + } + $tmp = array_map( 'trim', explode( ',', $attributes['requiredvars'] ) ); + $attributes['requiredvars'] = array(); + foreach( $tmp as $var ) { + + $pos = strpos( $var, '=' ); + if ($pos !== false) { + $val = trim(substr( $var, $pos+1 )); + $var = trim(substr( $var, 0, $pos )); + } else { + $val = null; + } + $var = strtoupper($var); + $pos = strpos( $var, '.' ); + + if ($pos === false) { + array_push( $attributes['requiredvars'], array( $templatename, $var, $val ) ); + } else { + array_push( $attributes['requiredvars'], array( + strtolower( substr( $var, 0, $pos ) ), + substr( $var, $pos+1 ), + $val + ) + ); + } + + } + $attributes['autoclear'] = 'yes'; + break; + + /** + * oddeven => switch to new modulo syntax + */ + case 'oddeven': + $attributes['type'] = 'modulo'; + $attributes['modulo'] = 2; + $attributes['autoclear'] = 'yes'; + break; + + /** + * modulo => requires a module attribute + */ + case 'modulo': + if( !isset( $attributes['modulo'] ) ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( "Attribute 'modulo' missing for $templatename" ) + ); + } + $attributes['autoclear'] = 'yes'; + break; + + /** + * standard template => do nothing + */ + case 'standard': + break; + + /** + * unknown type + */ + default: + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( "Unknown value for attribute type: {$attributes['type']}" ) + ); + break; + } + + $attributes['__prepared'] = true; + + return $attributes; + } + + /** + * build a template name + * + * @access private + * @return string new template name + */ + function _buildTemplateName() + { + return strtolower( uniqid( 'tmpl' ) ); + } + + /** + * close the current template + * + * @access private + * @return boolean true on success + */ + function _closeTemplate( $tmpl, $data ) + { + $name = array_pop( $this->_path ); + + $data = $this->_adjustWhitespace( $data, $tmpl['attributes']['whitespace'] ); + + array_pop( $this->_inheritAtts ); + + /** + * check for special templates + */ + switch( $tmpl['attributes']['type'] ) + { + /** + * check for whitespace in conditional templates + * and raise a notice + */ + case 'condition': + case 'modulo': + if( trim( $data ) != '' ) { + patErrorManager::raiseNotice( + PATTEMPLATE_READER_NOTICE_INVALID_CDATA_SECTION, + $this->_createErrorMessage( sprintf( 'No cdata is allowed inside a template of type %s (cdata was found in %s)', $tmpl['attributes']['type'], $tmpl['name'] ) ) + ); + } + $data = null; + break; + } + + /** + * store the content + */ + $tmpl['content'] = $data; + + /** + * No external template + */ + if( !isset( $tmpl['attributes']['src'] ) ) { + $tmpl['loaded'] = true; + } + + /** + * add it to the dependencies + */ + if( !empty( $this->_tmplStack ) ) { + $this->_addToParentTag( 'dependencies', $name ); + + if( isset( $tmpl['attributes']['placeholder'] ) ) { + // maintain BC + if( $this->shouldMaintainBc() && $tmpl['attributes']['placeholder'] === 'none' ) { + $tmpl['attributes']['placeholder'] = '__none'; + } + + if( $tmpl['attributes']['placeholder'] !== '__none' ) { + $this->_characterData( $this->_startTag.(strtoupper( $tmpl['attributes']['placeholder'] ) ).$this->_endTag ); + } + } else { + $this->_characterData( sprintf( "%sTMPL:%s%s", $this->_startTag, strtoupper( $name ), $this->_endTag ) ); + } + } + + unset( $tmpl['name'] ); + unset( $tmpl['tag'] ); + + $this->_templates[$name] = $tmpl; + + return true; + } + + /** + * create a new sub-template + * + * @access private + * @param array attributes + * @return boolean true on success + */ + function _initSubTemplate( $attributes ) + { + /** + * has to be embedded in a 'tmpl' tag + */ + if (!$this->_parentTagIs('tmpl')) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_TAG, + $this->_createErrorMessage( 'A subtemplate is only allowed in a TMPL tag' ) + ); + } + + /** + * needs a condition attribute + */ + if (!isset( $attributes['condition'] )) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_CONDITION_SPECIFIED, + $this->_createErrorMessage( 'Missing \'condition\' attribute for subtemplate' ) + ); + } + $matches = array(); + $regexp = '/^'.$this->_startTag.'([^a-z]+[^\\\])'.$this->_endTag.'$/U'; + if (preg_match($regexp, $attributes['condition'], $matches)) { + $attributes['var'] = $matches[1]; + } + + /** + * maintain BC + */ + if( $this->shouldMaintainBc() && in_array( $attributes['condition'], array( 'default', 'empty', 'odd', 'even' ) ) ) { + $attributes['condition'] = '__' . $attributes['condition']; + } + + if( $attributes['condition'] == '__odd' ) { + $attributes['condition'] = 1; + } elseif( $attributes['condition'] == '__even' ) { + $attributes['condition'] = 0; + } + + $parent = array_pop( $this->_tmplStack ); + array_push( $this->_tmplStack, $parent ); + if ($parent['attributes']['type'] == 'modulo') { + + if( preg_match( '/^\d$/', $attributes['condition'] ) ) { + if( (integer)$attributes['condition'] >= $parent['attributes']['modulo'] ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_INVALID_CONDITION, + $this->_createErrorMessage( 'Condition may only be between 0 and '.($parent['attributes']['modulo']-1) ) + ); + } + } + } + + $attributes = $this->_inheritAttributes( $attributes ); + + $condition = $attributes['condition']; + unset( $attributes['condition'] ); + + $subTmpl = array( + 'type' => 'sub', + 'condition' => $condition, + 'data' => '', + 'attributes' => $attributes, + 'comments' => array(), + 'dependencies' => array() + ); + + return $subTmpl; + } + + /** + * close subtemplate + * + * @access private + * @param string data + * @return boolean true on success + */ + function _closeSubTemplate( $subTmpl, $data ) + { + $data = $this->_adjustWhitespace( $data, $subTmpl['attributes']['whitespace'] ); + + $subTmpl['data'] = $data; + $condition = $subTmpl['condition']; + unset( $subTmpl['condition'] ); + + $this->_addToParentTemplate( 'subtemplates', + $subTmpl, + $condition + ); + return true; + } + + /** + * handle a variable + * + * @access private + * @param array attributes of the var tag + * @param string cdata between the tags (will be used as default) + * @return boolean true on success + */ + function _handleVariable( $attributes, $data ) + { + if( !isset( $attributes['name'] ) ) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_NAME_SPECIFIED, + $this->_createErrorMessage( 'Variable needs a name attribute' ) + ); + } + + $specs = array(); + + /** + * get name + */ + $name = strtoupper( $attributes['name'] ); + unset( $attributes['name'] ); + $specs['name'] = $name; + + /** + * use data as default value + */ + if( isset( $attributes['default'] ) ) { + $data = $attributes['default']; + $specs['default'] = $data; + unset( $attributes['default'] ); + } elseif (!empty( $data )) { + $specs['default'] = $data; + } + + /** + * add it to template, if it's not hidden + */ + if (!isset( $attributes['hidden'] ) || $attributes['hidden'] == 'no') { + $this->_characterData( $this->_startTag . strtoupper( $name ) . $this->_endTag ); + } + + if( isset( $attributes['hidden'] ) ) { + unset( $attributes['hidden'] ); + } + + /** + * copy value from any other variable + */ + if (isset( $attributes['copyfrom'] )) { + $specs['copyfrom'] = strtoupper( $attributes['copyfrom'] ); + + if (strstr( $specs['copyfrom'], '.' )) { + $specs['copyfrom'] = explode( '.', $specs['copyfrom'] ); + $specs['copyfrom'][0] = strtolower( $specs['copyfrom'][0] ); + } + + unset( $attributes['copyfrom'] ); + } + + if( isset( $attributes['modifier'] ) ) { + $modifier = $attributes['modifier']; + unset( $attributes['modifier'] ); + + $type = isset( $attributes['modifiertype'] ) ? $attributes['modifiertype'] : 'auto'; + + if( isset( $attributes['modifiertype'] ) ) + unset( $attributes['modifiertype'] ); + + $specs['modifier'] = array( 'mod' => $modifier, 'type' => $type, 'params' => $attributes ); + } + + if (!empty( $specs )) { + $this->_addToParentTemplate( + 'varspecs', + $specs, + $name + ); + } + return true; + } + + + /** + * handle a comment + * + * @access private + * @param array attributes of the comment tag + * @param string cdata between the tags (will be used as default) + * @return boolean true on success + */ + function _handleComment( $attributes, $data ) + { + $this->_addToParentTag( 'comments', $data ); + } + + /** + * get the character data of the element + * + * @access private + * @return string + */ + function _getCData() + { + if( $this->_depth == 0 ) { + return ''; + } + return $this->_data[$this->_depth]; + } + + /** + * add to a property of the parent template + * + * @access private + * @param string property to add to + * @param mixed value to add + * @param string key + */ + function _addToParentTemplate( $property, $value, $key = null ) + { + $cnt = count( $this->_tmplStack ); + + if ($cnt === 0) { + return false; + } + + $pos = $cnt - 1; + while ($pos >= 0) { + if ($this->_tmplStack[$pos]['type'] != 'tmpl') { + $pos--; + continue; + } + + if ($key === null) { + + if (!in_array( $value, $this->_tmplStack[$pos][$property] )) { + array_push( $this->_tmplStack[$pos][$property], $value ); + } + } else { + $this->_tmplStack[$pos][$property][$key] = $value; + } + + return true; + } + + return false; + } + + /** + * get a property of the parent template + * + * @access private + * @param string property to add to + * @return mixed value to add + */ + function _getFromParentTemplate( $property ) + { + $cnt = count( $this->_tmplStack ); + + if ($cnt === 0) { + return false; + } + + $pos = $cnt - 1; + while ($pos >= 0) { + if( $this->_tmplStack[$pos]['type'] != 'tmpl' ) { + $pos--; + continue; + } + + if (isset( $this->_tmplStack[$pos][$property] )) { + return $this->_tmplStack[$pos][$property]; + } + + return false; + } + return false; + } + + + /** + * add to a property of the parent tag + * + * @access private + * @param string property to add to + * @param mixed value to add + * @param string key + */ + function _addToParentTag( $property, $value, $key = null ) + { + $cnt = count( $this->_tmplStack ); + + if ($cnt === 0) { + return false; + } + + $pos = $cnt - 1; + + if ($key === null) { + + if (!in_array( $value, $this->_tmplStack[$pos][$property] )) { + array_push( $this->_tmplStack[$pos][$property], $value ); + } + } else { + $this->_tmplStack[$pos][$property][$key] = $value; + } + + return true; + } + + /** + * adjust whitespace in a CData block + * + * @access private + * @param string data + * @param string behaviour + * @return string data + */ + function _adjustWhitespace( $data, $behaviour ) + { + switch( $behaviour ) { + case 'trim': + $data = str_replace( '\n', ' ', $data ); + $data = preg_replace( '/\s\s+/', ' ', $data ); + $data = trim( $data ); + break; + } + return $data; + } + + /** + * inherit attributes from the parent template + * + * The following attributes are inherited automatically: + * - whitespace + * - unusedvars + * + * @access private + * @param array attributes + * @param array attributes with inherited attributes + * @return array new attribute collection + */ + function _inheritAttributes( $attributes ) + { + if (!empty( $this->_inheritAtts )) { + $parent = end( $this->_inheritAtts ); + } else { + $parent = array( + 'whitespace' => $this->_defaultAtts['whitespace'], + 'unusedvars' => $this->_defaultAtts['unusedvars'], + 'autoclear' => $this->_defaultAtts['autoclear'] + ); + } + + $attributes = array_merge( $parent, $attributes ); + + return $attributes; + } + + /** + * checks, whether the parent tag is of a certain type + * + * This is needed to ensure, that subtemplates are only + * placed inside a template + * + * @access private + * @param string type (tmpl, sub, var, link) + * @return boolean + */ + function _parentTagIs( $type ) + { + $parent = array_pop( $this->_tmplStack ); + if( $parent === null ) { + return false; + } + array_push( $this->_tmplStack, $parent ); + + if( $parent['type'] == $type ) { + return true; + } + + return false; + } + + /** + * get the current line number + * + * @access private + * @return integer line number + */ + function _getCurrentLine() + { + $line = count( explode( "\n", $this->_processedData ) ); + return $line; + } + + /** + * create an error message + * + * This method takes an error messages and appends the + * current line number as well as a pointer to the input + * (filename) + * + * @access private + * @param string base error message + * @return strin error message + */ + function _createErrorMessage( $msg ) + { + return sprintf( '%s in %s on line %d', $msg, $this->getCurrentInput(), $this->_getCurrentLine() ); + } + + /** + * get the current input + * + * @access public + * @return string + */ + function getCurrentInput() + { + return $this->_currentInput; + } + + /** + * tests whether the reader should maintain backwards compatibility + * + * If enabled, you can still use 'default', 'empty', 'odd' and 'even' + * instead of '__default', '__empty', etc. + * + * This will be disabled by default in future versions. + * + * @access public + * @return boolean + */ + function shouldMaintainBc() + { + if (!isset( $this->_options['maintainBc'] )) { + return false; + } + return $this->_options['maintainBc']; + } + + /** + * returns, whether the reader currently is in use + * + * @access public + * @return boolean + */ + function isInUse() + { + return $this->_inUse; + } + + /** + * get the template root for this reader + * + * @access public + * @return string + */ + function getTemplateRoot() + { + if (!isset($this->_options['root'])) { + return null; + } + if (isset($this->_options['root'][$this->_name])) { + return $this->_options['root'][$this->_name]; + } + if (isset($this->_options['root']['__default'])) { + return $this->_options['root']['__default']; + } + return null; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/DB.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/DB.php new file mode 100644 index 00000000..740fe645 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/DB.php @@ -0,0 +1,164 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * PEAR::DB is not installed + */ +define('PATTEMPLATE_READER_DB_ERROR_CLASS_NOT_FOUND', 'patTemplate::Reader::DB::001'); + +/** + * Connection could not be established + */ +define('PATTEMPLATE_READER_DB_ERROR_NO_CONNECTION', 'patTemplate::Reader::DB::002'); + +/** + * Could not find input + */ +define('PATTEMPLATE_READER_DB_ERROR_NO_INPUT', 'patTemplate::Reader::DB::003'); + +/** + * Unknown input syntax + */ +define('PATTEMPLATE_READER_DB_ERROR_UNKNOWN_INPUT', 'patTemplate::Reader::DB::004'); + +/** + * patTemplate Reader that reads from a database using PEAR::DB + * + * $Id: DB.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Readers + * @author Stephan Schmidt + */ +class patTemplate_Reader_DB extends patTemplate_Reader +{ + /** + * reader name + * @access private + * @var string + */ + var $_name = 'DB'; + + /** + * read templates from the database + * + * Input may either be an SQL query or a string defining the location + * of the template using the format: + * + * table[@key=value]/@templateField + * + * + * @final + * @access public + * @param string file to parse + * @return array templates + */ + function readTemplates($input) + { + $content = $this->getDataFromDb($input); + if (patErrorManager::isError($content)) { + return $content; + } + $templates = $this->parseString($content); + return $templates; + } + + /** + * fetch the template data from the database + * + * @access protected + * @param string input to read from + */ + function getDataFromDb($input) + { + // check for PEAR DB + if (!class_exists('DB')) { + @include_once 'DB.php'; + if (!class_exists('DB')) { + return patErrorManager::raiseError(PATTEMPLATE_READER_DB_ERROR_CLASS_NOT_FOUND, 'This reader requires PEAR::DB which could not be found on your system.'); + } + } + + // establish connection + $db = &DB::connect($this->getTemplateRoot()); + if (PEAR::isError($db)) { + return patErrorManager::raiseError(PATTEMPLATE_READER_DB_ERROR_NO_CONNECTION, 'Could not establish database connection: ' . $db->getMessage()); + } + + $input = $this->parseInputStringToQuery($input, $db); + if (patErrorManager::isError($input)) { + return $input; + } + + $content = $db->getOne($input); + if (PEAR::isError($content)) { + return patErrorManager::raiseError(PATTEMPLATE_READER_DB_ERROR_NO_INPUT, 'Could not fetch template: ' . $content->getMessage()); + } + return $content; + } + + /** + * Parse the template location syntax to a query + * + * @access private + * @param string + * @param DB_common + */ + function parseInputStringToQuery($input, $db) + { + // Input is no query + if (strstr($input, 'SELECT') !== false) { + return $input; + } + + $matches = array(); + if (!preg_match('/^([a-z]+)\[([^]]+)\]\/@([a-z]+)$/i', $input, $matches)) { + return patErrorManager::raiseError(PATTEMPLATE_READER_DB_ERROR_UNKNOWN_INPUT, 'Could not parse input string.'); + } + + $table = $matches[1]; + $templateField = $matches[3]; + $where = array(); + $tmp = explode(',', $matches[2]); + foreach ($tmp as $clause) { + list($field, $value) = explode('=', trim($clause)); + if ($field{0} !== '@') { + return patErrorManager::raiseError(PATTEMPLATE_READER_DB_ERROR_UNKNOWN_INPUT, 'Could not parse input string.'); + } + $field = substr($field, 1); + array_push($where, $field . '=' . $db->quoteSmart($value)); + } + + $query = sprintf('SELECT %s FROM %s WHERE %s', $templateField, $table, implode(' AND ', $where)); + return $query; + } + + /** + * load template from any input + * + * If the a template is loaded, the content will not get + * analyzed but the whole content is returned as a string. + * + * @abstract must be implemented in the template readers + * @param mixed input to load from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @return string template content + */ + function loadTemplate($input) + { + $content = $this->getDataFromDb($input); + return $content; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/File.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/File.php new file mode 100644 index 00000000..88fe9908 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/File.php @@ -0,0 +1,174 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate Reader that reads from a file + * + * $Id: File.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Readers + * @author Stephan Schmidt + */ +class patTemplate_Reader_File extends patTemplate_Reader +{ + /** + * reader name + * @access private + * @var string + */ + var $_name = 'File'; + + /** + * flag to indicate, that current file is remote + * + * @access private + * @var boolean + */ + var $_isRemote = false; + + /** + * all files, that have been opened + * + * @access private + * @var array + */ + var $_files = array(); + + /** + * read templates from any input + * + * @final + * @access public + * @param string file to parse + * @return array templates + */ + function readTemplates( $input ) + { + if (isset($this->_rootAtts['relative'])) { + $relative = $this->_rootAtts['relative']; + } else { + $relative = false; + } + if ($relative === false) { + $this->_currentInput = $input; + } else { + $this->_currentInput = dirname($relative) . DIRECTORY_SEPARATOR . $input; + } + + $fullPath = $this->_resolveFullPath($input, $relative); + if (patErrorManager::isError($fullPath)) { + return $fullPath; + } + $content = $this->_getFileContents($fullPath); + if (patErrorManager::isError($content)) { + return $content; + } + + $templates = $this->parseString($content); + + return $templates; + } + + /** + * load template from any input + * + * If the a template is loaded, the content will not get + * analyzed but the whole content is returned as a string. + * + * @abstract must be implemented in the template readers + * @param mixed input to load from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @return string template content + */ + function loadTemplate( $input ) + { + if (isset($this->_rootAtts['relative'])) { + $relative = $this->_rootAtts['relative']; + } else { + $relative = false; + } + $fullPath = $this->_resolveFullPath( $input, $relative ); + if( patErrorManager::isError( $fullPath ) ) + return $fullPath; + return $this->_getFileContents( $fullPath ); + } + + /** + * resolve path for a template + * + * @access private + * @param string filename + * @param boolean|string filename for relative path calculation + * @return string full path + */ + function _resolveFullPath( $filename, $relativeTo = false ) + { + if (preg_match( '/^[a-z]+:\/\//', $filename )) { + $this->_isRemote = true; + return $filename; + } else { + $rootFolders = $this->getTemplateRoot(); + if (!is_array($rootFolders)) { + $rootFolders = array($rootFolders); + } + foreach ($rootFolders as $root) { + if ($relativeTo === false) { + $baseDir = $root; + } else { + $baseDir = $root . DIRECTORY_SEPARATOR . dirname($relativeTo); + } + $fullPath = $baseDir . DIRECTORY_SEPARATOR . $filename; + if (file_exists($fullPath)) { + return $fullPath; + } + } + } + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_INPUT, + "Could not load templates from $filename." + ); + } + + /** + * get the contents of a file + * + * @access private + * @param string filename + * @return string file contents + */ + function _getFileContents( $file ) + { + if (!$this->_isRemote && (!file_exists($file) || !is_readable($file))) { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_INPUT, + "Could not load templates from $file." + ); + } + + if (function_exists('file_get_contents')) { + $content = @file_get_contents( $file ); + } else { + $content = implode('', file($file)); + } + + /** + * store the file name + */ + array_push($this->_files, $file); + + return $content; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/IT.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/IT.php new file mode 100644 index 00000000..84e30a03 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/IT.php @@ -0,0 +1,233 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate reader that reads HTML_Template_IT files + * + * @package patTemplate + * @subpackage Readers + * @author Stephan Schmidt + */ +class patTemplate_Reader_IT extends patTemplate_Reader +{ + /** + * reader name + * @access private + * @var string + */ + var $_name = 'IT'; + + /** + * files that have been used + * @access private + * @var array + */ + var $_files = array(); + + /** + * parse templates from string + * + * @access private + * @param string string to parse + * @return array templates + */ + function parseString( $string ) + { + /** + * apply input filter before parsing + */ + $string = $this->_tmpl->applyInputFilters( $string ); + + $this->_inheritAtts = array(); + $this->_elStack = array(); + $this->_data = array( '' ); + $this->_tmplStack = array(); + $this->_depth = 0; + $this->_templates = array(); + $this->_path = array(); + $this->_processedData = ''; + + $this->_defaultAtts = $this->_tmpl->getDefaultAttributes(); + + if( !isset( $this->_defaultAtts['autoload'] ) ) + $this->_defaultAtts['autoload'] = 'on'; + + /** + * create a special root template + */ + $attributes = $this->_rootAtts; + $attributes['name'] = '__global'; + + $rootTemplate = $this->_initTemplate( $attributes ); + + array_push( $this->_tmplStack, $rootTemplate ); + + /** + *start parsing + */ + $patNamespace = strtolower( $this->_tmpl->getNamespace() ); + + $regexp = '/()/m'; + + $tokens = preg_split( $regexp, $string, -1, PREG_SPLIT_DELIM_CAPTURE ); + + /** + * the first token is always character data + * Though it could just be empty + */ + if( $tokens[0] != '' ) + $this->_characterData( $tokens[0] ); + + $cnt = count( $tokens ); + $i = 1; + // process all tokens + while( $i < $cnt ) + { + $fullTag = $tokens[$i++]; + $closing = strtoupper( $tokens[$i++] ) == 'END' ? true : false; + $tmplName = $tokens[$i++]; + $namespace = $patNamespace; + $tagname = 'tmpl'; + $data = $tokens[$i++]; + + /** + * is it a closing tag? + */ + if( $closing === true ) + { + $result = $this->_endElement( $namespace, $tagname ); + if( patErrorManager::isError( $result ) ) + { + return $result; + } + $this->_characterData( $data ); + continue; + } + + $attributes = array( 'name' => $tmplName ); + $result = $this->_startElement( $namespace, $tagname, $attributes ); + if( patErrorManager::isError( $result ) ) + { + return $result; + } + + $this->_characterData( $data ); + } + + $rootTemplate = array_pop( $this->_tmplStack ); + + $this->_closeTemplate( $rootTemplate, $this->_data[0] ); + + /** + * check for tags that are still open + */ + if( $this->_depth > 0 ) + { + $el = array_pop( $this->_elStack ); + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_CLOSING_TAG, + $this->_createErrorMessage( "No closing tag for {$el['ns']}:{$el['name']} found" ) + ); + } + + return $this->_templates; + } + + /** + * read templates from any input + * + * @final + * @access public + * @param string file to parse + * @return array templates + */ + function readTemplates( $input ) + { + $this->_currentInput = $input; + $fullPath = $this->_resolveFullPath( $input ); + if( patErrorManager::isError( $fullPath ) ) + return $fullPath; + $content = $this->_getFileContents( $fullPath ); + if( patErrorManager::isError( $content ) ) + return $content; + + $templates = $this->parseString( $content ); + + return $templates; + } + + /** + * load template from any input + * + * If the a template is loaded, the content will not get + * analyzed but the whole content is returned as a string. + * + * @abstract must be implemented in the template readers + * @param mixed input to load from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @return string template content + */ + function loadTemplate( $input ) + { + $fullPath = $this->_resolveFullPath( $input ); + if( patErrorManager::isError( $fullPath ) ) + return $fullPath; + return $this->_getFileContents( $fullPath ); + } + + /** + * resolve path for a template + * + * @access private + * @param string filename + * @return string full path + */ + function _resolveFullPath( $filename ) + { + $baseDir = $this->getTemplateRoot(); + $fullPath = $baseDir . '/' . $filename; + return $fullPath; + } + + /** + * get the contents of a file + * + * @access private + * @param string filename + * @return string file contents + */ + function _getFileContents( $file ) + { + if( !file_exists( $file ) || !is_readable( $file ) ) + { + return patErrorManager::raiseError( + PATTEMPLATE_READER_ERROR_NO_INPUT, + "Could not load templates from $file." + ); + } + + if( function_exists( 'file_get_contents' ) ) + $content = @file_get_contents( $file ); + else + $content = implode( '', file( $file ) ); + + /** + * store the file name + */ + array_push( $this->_files, $file ); + + return $content; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/String.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/String.php new file mode 100644 index 00000000..5a4a8e19 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/String.php @@ -0,0 +1,42 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate Reader that reads from a string + * + * @package patTemplate + * @subpackage Readers + * @author Stephan Schmidt + */ +class patTemplate_Reader_String extends patTemplate_Reader +{ + /** + * Read templates from a string + * + * @final + * @access public + * @param string string to parse + * @param array options, not implemented in current versions, but future versions will allow passing of options + * @return array templates + */ + function readTemplates( $input ) + { + $this->_currentInput = $input; + + $templates = $this->parseString( $input ); + + return $templates; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Reader/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat.php new file mode 100644 index 00000000..83188679 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat.php @@ -0,0 +1,70 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate Stat + * + * $Id: Stat.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * A stat component should be implemented for each reader + * to support caching. Stats return information about the + * template source. + * + * @package patTemplate + * @subpackage Stat + * @author Stephan Schmidt + * @abstract + */ +class patTemplate_Stat extends patTemplate_Module +{ + /** + * options, are identical to those of the corresponding reader + * + * @access private + * @var array + */ + var $_options = array(); + + /** + * get the modification time of a template + * + * Needed, if a template cache should be used, that auto-expires + * the cache. + * + * @abstract must be implemented in the template readers + * @param mixed input to read from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @return integer unix timestamp + */ + function getModificationTime( $input ) + { + return -1; + } + + /** + * set options + * + * @access public + * @param array array containing options + */ + function setOptions( $options ) + { + $this->_options = $options; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/File.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/File.php new file mode 100644 index 00000000..0a0b59d4 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/File.php @@ -0,0 +1,51 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate Stat + * + * $Id: File.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * A stat component should be implemented for each reader + * to support caching. Stats return information about the + * template source. + * + * @package patTemplate + * @subpackage Stat + * @author Stephan Schmidt + */ +class patTemplate_Stat_File extends patTemplate_Stat +{ + /** + * get the modification time of a template + * + * Needed, if a template cache should be used, that auto-expires + * the cache. + * + * @abstract must be implemented in the template readers + * @param mixed input to read from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @return integer unix timestamp + */ + function getModificationTime( $input ) + { + $fullPath = $this->_options['root'] . '/' . $input; + return @filemtime( $fullPath ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/Stat/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache.php new file mode 100644 index 00000000..9aa337bc --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache.php @@ -0,0 +1,83 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * Base class for patTemplate template cache + * + * $Id: TemplateCache.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * A template cache is used to cache the data after + * the template has been read, but before the variables + * have been added. + * + * Data is normally stored in serialized format. This + * will increase performance. + * + * This is not related to an output cache! + * + * @abstract + * @package patTemplate + * @subpackage Caches + * @author Stephan Schmidt + */ +class patTemplate_TemplateCache extends patTemplate_Module +{ + /** + * load template from cache + * + * @access public + * @param string cache key + * @param integer modification time of original template + * @return array|boolean either an array containing the templates or false cache could not be loaded + */ + function load( $key, $modTime = -1 ) + { + return false; + } + + /** + * write template to cache + * + * @access public + * @param string cache key + * @param array templates to store + */ + function write( $key, $templates ) + { + return true; + } + + /** + * get the cache key for the input + * + * @param mixed input to read from. + * This can be a string, a filename, a resource or whatever the derived class needs to read from + * @param array options + * @return string key + */ + function getKey( $input, $options = array() ) + { + return md5( $input . serialize( $options ) ); + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/File.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/File.php new file mode 100644 index 00000000..74598a05 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/File.php @@ -0,0 +1,118 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate Template cache that stores data on filesystem + * + * $Id: File.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * Possible parameters for the cache are: + * - cacheFolder : set the folder from which to load the cache + * - lifetime : seconds for which the cache is valid, if set to auto, it will check + * whether the cache is older than the original file (if the reader supports this) + * - prefix for the filenames + * + * @package patTemplate + * @subpackage Caches + * @author Stephan Schmidt + */ +class patTemplate_TemplateCache_File extends patTemplate_TemplateCache +{ + /** + * parameters of the cache + * + * @access private + * @var array + */ + var $_params = array( + 'cacheFolder' => './cache', + 'lifetime' => 'auto', + 'prefix' => '', + 'filemode' => null + ); + + + /** + * load template from cache + * + * @access public + * @param string cache key + * @param integer modification time of original template + * @return array|boolean either an array containing the templates or false cache could not be loaded + */ + function load($key, $modTime = -1) + { + $filename = $this->_getCachefileName( $key ); + if (!file_exists($filename) || !is_readable($filename)) { + return false; + } + + $generatedOn = filemtime( $filename ); + $ttl = $this->getParam('lifetime'); + if ($ttl == 'auto') { + if ($modTime < 1) { + return false; + } + if ($modTime > $generatedOn) { + return false; + } + return unserialize(file_get_contents($filename)); + } elseif(is_int( $ttl)) { + if ($generatedOn + $ttl < time()) { + return false; + } + return unserialize( file_get_contents( $filename ) ); + } + + return false; + } + + /** + * write template to cache + * + * @access public + * @param string cache key + * @param array templates to store + * @return boolean true on success + */ + function write( $key, $templates ) + { + $cacheFile = $this->_getCachefileName($key); + $fp = @fopen($cacheFile, 'w'); + if (!$fp) { + return false; + } + flock( $fp, LOCK_EX ); + fputs( $fp, serialize($templates)); + flock( $fp, LOCK_UN ); + $filemode = $this->getParam('filemode'); + if ($filemode !== null) { + chmod($cacheFile, $filemode); + } + return true; + } + + /** + * get the cache filename + * + * @access private + * @param string cache key + * @return string cache file name + */ + function _getCachefileName( $key ) + { + return $this->getParam( 'cacheFolder' ) . '/' . $this->getParam( 'prefix' ).$key . '.cache'; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/MMCache.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/MMCache.php new file mode 100644 index 00000000..af7683f4 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/MMCache.php @@ -0,0 +1,84 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate Template cache that stores data in the eAccelerator Cache + * + * If the lifetime is set to auto, the cache files will be kept until + * you delete them manually. + * + * $Id: MMCache.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Caches + * @author Mike Valstar + */ +class patTemplate_TemplateCache_MMCache extends patTemplate_TemplateCache +{ + /** + * parameters of the cache + * + * @access private + * @var array + */ + var $_params = array( 'lifetime' => 'auto'); + + /** + * load template from cache + * + * @access public + * @param string cache key + * @param integer modification time of original template + * @return array|boolean either an array containing the templates or false cache could not be loaded + */ + function load( $key, $modTime = -1 ) + { + if (!function_exists('mmcache_lock')) { + return false; + } + + $something = mmcache_get($key); + if (is_null($something)){ + return false; + }else{ + return unserialize($something); + } + } + + /** + * write template to cache + * + * @access public + * @param string cache key + * @param array templates to store + * @return boolean true on success + */ + function write( $key, $templates ) + { + if (!function_exists('mmcache_lock')) { + return false; + } + + mmcache_lock($key); + if ($this->getParam( 'lifetime' ) == 'auto'){ + mmcache_put($key, serialize( $templates )); + }else{ + mmcache_put($key, serialize( $templates ), $this->getParam( 'lifetime' ) * 60); + } + mmcache_unlock($key); + + return true; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/eAccelerator.php b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/eAccelerator.php new file mode 100644 index 00000000..5e4ed0b9 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/eAccelerator.php @@ -0,0 +1,83 @@ + + */ + +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * patTemplate Template cache that stores data in the eAccelerator Cache + * + * If the lifetime is set to auto, the cache files will be kept until + * you delete them manually. + * + * $Id: eAccelerator.php 10381 2008-06-01 03:35:53Z pasamio $ + * + * @package patTemplate + * @subpackage Caches + * @author Mike Valstar + */ +class patTemplate_TemplateCache_eAccelerator extends patTemplate_TemplateCache +{ + /** + * parameters of the cache + * + * @access private + * @var array + */ + var $_params = array( 'lifetime' => 'auto'); + + /** + * load template from cache + * + * @access public + * @param string cache key + * @param integer modification time of original template + * @return array|boolean either an array containing the templates or false cache could not be loaded + */ + function load( $key, $modTime = -1 ) + { + if (!function_exists('eaccelerator_lock')) { + return false; + } + $something = eaccelerator_get($key); + if (is_null($something)){ + return false; + }else{ + return unserialize($something); + } + } + + /** + * write template to cache + * + * @access public + * @param string cache key + * @param array templates to store + * @return boolean true on success + */ + function write( $key, $templates ) + { + if (!function_exists('eaccelerator_lock')) { + return false; + } + + eaccelerator_lock($key); + if ($this->getParam( 'lifetime' ) == 'auto'){ + eaccelerator_put($key, serialize( $templates )); + }else{ + eaccelerator_put($key, serialize( $templates ), $this->getParam( 'lifetime' ) * 60); + } + eaccelerator_unlock($key); + + return true; + } +} +?> diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/TemplateCache/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/oss/joomla/trunk/libraries/pattemplate/patTemplate/index.html b/oss/joomla/trunk/libraries/pattemplate/patTemplate/index.html new file mode 100644 index 00000000..88a5baf7 --- /dev/null +++ b/oss/joomla/trunk/libraries/pattemplate/patTemplate/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file