include external libs in a subdir to make install easier
darcs-hash:20080822131714-84dde-6978424ded2ed1041a65142a25560654ac717fcd.gz
This commit is contained in:
parent
dfdc8b7773
commit
14c9366aac
552
extlib/Auth/OpenID.php
Normal file
552
extlib/Auth/OpenID.php
Normal file
@ -0,0 +1,552 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This is the PHP OpenID library by JanRain, Inc.
|
||||
*
|
||||
* This module contains core utility functionality used by the
|
||||
* library. See Consumer.php and Server.php for the consumer and
|
||||
* server implementations.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* The library version string
|
||||
*/
|
||||
define('Auth_OpenID_VERSION', '2.1.1');
|
||||
|
||||
/**
|
||||
* 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("<html>".
|
||||
"<head><title>".
|
||||
$title .
|
||||
"</title></head>".
|
||||
"<body onload='document.forms[0].submit();'>".
|
||||
$form .
|
||||
"<script>".
|
||||
"var elements = document.forms[0].elements;".
|
||||
"for (var i = 0; i < elements.length; i++) {".
|
||||
" elements[i].style.display = \"none\";".
|
||||
"}".
|
||||
"</script>".
|
||||
"</body>".
|
||||
"</html>");
|
||||
}
|
||||
}
|
||||
?>
|
1023
extlib/Auth/OpenID/AX.php
Normal file
1023
extlib/Auth/OpenID/AX.php
Normal file
File diff suppressed because it is too large
Load Diff
613
extlib/Auth/OpenID/Association.php
Normal file
613
extlib/Auth/OpenID/Association.php
Normal file
@ -0,0 +1,613 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module contains code for dealing with associations between
|
||||
* consumers and servers.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
471
extlib/Auth/OpenID/BigMath.php
Normal file
471
extlib/Auth/OpenID/BigMath.php
Normal file
@ -0,0 +1,471 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* BigMath: A math library wrapper that abstracts out the underlying
|
||||
* long integer library.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
?>
|
2227
extlib/Auth/OpenID/Consumer.php
Normal file
2227
extlib/Auth/OpenID/Consumer.php
Normal file
File diff suppressed because it is too large
Load Diff
109
extlib/Auth/OpenID/CryptUtil.php
Normal file
109
extlib/Auth/OpenID/CryptUtil.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CryptUtil: A suite of wrapper utility functions for the OpenID
|
||||
* library.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
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
|
||||
* <code>Auth_OpenID_RAND_SOURCE</code> as <code>null</code>, 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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
131
extlib/Auth/OpenID/DatabaseConnection.php
Normal file
131
extlib/Auth/OpenID/DatabaseConnection.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The Auth_OpenID_DatabaseConnection class, which is used to emulate
|
||||
* a PEAR database connection.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
113
extlib/Auth/OpenID/DiffieHellman.php
Normal file
113
extlib/Auth/OpenID/DiffieHellman.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The OpenID library's Diffie-Hellman implementation.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
require_once 'Auth/OpenID.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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
548
extlib/Auth/OpenID/Discover.php
Normal file
548
extlib/Auth/OpenID/Discover.php
Normal file
@ -0,0 +1,548 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The OpenID and Yadis discovery implementation for OpenID 1.2.
|
||||
*/
|
||||
|
||||
require_once "Auth/OpenID.php";
|
||||
require_once "Auth/OpenID/Parse.php";
|
||||
require_once "Auth/OpenID/Message.php";
|
||||
require_once "Auth/Yadis/XRIRes.php";
|
||||
require_once "Auth/Yadis/Yadis.php";
|
||||
|
||||
// XML namespace value
|
||||
define('Auth_OpenID_XMLNS_1_0', 'http://openid.net/xmlns/1.0');
|
||||
|
||||
// Yadis service types
|
||||
define('Auth_OpenID_TYPE_1_2', 'http://openid.net/signon/1.2');
|
||||
define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
|
||||
define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
|
||||
define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
|
||||
define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
|
||||
define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
|
||||
'http://specs.openid.net/auth/2.0/return_to');
|
||||
|
||||
function Auth_OpenID_getOpenIDTypeURIs()
|
||||
{
|
||||
return array(Auth_OpenID_TYPE_2_0_IDP,
|
||||
Auth_OpenID_TYPE_2_0,
|
||||
Auth_OpenID_TYPE_1_2,
|
||||
Auth_OpenID_TYPE_1_1,
|
||||
Auth_OpenID_TYPE_1_0,
|
||||
Auth_OpenID_RP_RETURN_TO_URL_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object representing an OpenID service endpoint.
|
||||
*/
|
||||
class Auth_OpenID_ServiceEndpoint {
|
||||
function Auth_OpenID_ServiceEndpoint()
|
||||
{
|
||||
$this->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 <link rel='...'> 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
|
||||
// <link rel="...">
|
||||
$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 <link
|
||||
// rel="...">
|
||||
$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;
|
||||
}
|
||||
|
||||
?>
|
100
extlib/Auth/OpenID/DumbStore.php
Normal file
100
extlib/Auth/OpenID/DumbStore.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file supplies a dumb store backend for OpenID servers and
|
||||
* consumers.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
62
extlib/Auth/OpenID/Extension.php
Normal file
62
extlib/Auth/OpenID/Extension.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* An interface for OpenID extensions.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the Message implementation.
|
||||
*/
|
||||
require_once 'Auth/OpenID/Message.php';
|
||||
|
||||
/**
|
||||
* A base class for accessing extension request and response data for
|
||||
* the OpenID 2 protocol.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_Extension {
|
||||
/**
|
||||
* ns_uri: The namespace to which to add the arguments for this
|
||||
* extension
|
||||
*/
|
||||
var $ns_uri = null;
|
||||
var $ns_alias = null;
|
||||
|
||||
/**
|
||||
* Get the string arguments that should be added to an OpenID
|
||||
* message for this extension.
|
||||
*/
|
||||
function getExtensionArgs()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the arguments from this extension to the provided message.
|
||||
*
|
||||
* Returns the message with the extension arguments added.
|
||||
*/
|
||||
function toMessage(&$message)
|
||||
{
|
||||
$implicit = $message->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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
618
extlib/Auth/OpenID/FileStore.php
Normal file
618
extlib/Auth/OpenID/FileStore.php
Normal file
@ -0,0 +1,618 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file supplies a Memcached store backend for OpenID servers and
|
||||
* consumers.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require 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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
99
extlib/Auth/OpenID/HMAC.php
Normal file
99
extlib/Auth/OpenID/HMAC.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This is the HMACSHA1 implementation for the OpenID library.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
require_once 'Auth/OpenID.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);
|
||||
}
|
||||
|
||||
?>
|
197
extlib/Auth/OpenID/Interface.php
Normal file
197
extlib/Auth/OpenID/Interface.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file specifies the interface for PHP OpenID store implementations.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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. <openid@janrain.com>
|
||||
*/
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
112
extlib/Auth/OpenID/KVForm.php
Normal file
112
extlib/Auth/OpenID/KVForm.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenID protocol key-value/comma-newline format parsing and
|
||||
* serialization
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
208
extlib/Auth/OpenID/MemcachedStore.php
Normal file
208
extlib/Auth/OpenID/MemcachedStore.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file supplies a memcached store backend for OpenID servers and
|
||||
* consumers.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author Artemy Tregubenko <me@arty.name>
|
||||
* @copyright 2008 JanRain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
* Contributed by Open Web Technologies <http://openwebtech.ru/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
915
extlib/Auth/OpenID/Message.php
Normal file
915
extlib/Auth/OpenID/Message.php
Normal file
@ -0,0 +1,915 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Extension argument processing code
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Import tools needed to deal with messages.
|
||||
*/
|
||||
require_once 'Auth/OpenID.php';
|
||||
require_once 'Auth/OpenID/KVForm.php';
|
||||
require_once 'Auth/Yadis/XML.php';
|
||||
require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse
|
||||
|
||||
// This doesn't REALLY belong here, but where is better?
|
||||
define('Auth_OpenID_IDENTIFIER_SELECT',
|
||||
"http://specs.openid.net/auth/2.0/identifier_select");
|
||||
|
||||
// URI for Simple Registration extension, the only commonly deployed
|
||||
// OpenID 1.x extension, and so a special case
|
||||
define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');
|
||||
|
||||
// The OpenID 1.X namespace URI
|
||||
define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
|
||||
define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');
|
||||
|
||||
function Auth_OpenID_isOpenID1($ns)
|
||||
{
|
||||
return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
|
||||
($ns == Auth_OpenID_OPENID1_NS);
|
||||
}
|
||||
|
||||
// The OpenID 2.0 namespace URI
|
||||
define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');
|
||||
|
||||
// The namespace consisting of pairs with keys that are prefixed with
|
||||
// "openid." but not in another namespace.
|
||||
define('Auth_OpenID_NULL_NAMESPACE', 'Null namespace');
|
||||
|
||||
// The null namespace, when it is an allowed OpenID namespace
|
||||
define('Auth_OpenID_OPENID_NS', 'OpenID namespace');
|
||||
|
||||
// The top-level namespace, excluding all pairs with keys that start
|
||||
// with "openid."
|
||||
define('Auth_OpenID_BARE_NS', 'Bare namespace');
|
||||
|
||||
// Sentinel for Message implementation to indicate that getArg should
|
||||
// return null instead of returning a default.
|
||||
define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');
|
||||
|
||||
// Limit, in bytes, of identity provider and return_to URLs, including
|
||||
// response payload. See OpenID 1.1 specification, Appendix D.
|
||||
define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);
|
||||
|
||||
// All OpenID protocol fields. Used to check namespace aliases.
|
||||
global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
|
||||
$Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
|
||||
'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
|
||||
'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
|
||||
'dh_consumer_public', 'claimed_id', 'identity', 'realm',
|
||||
'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
|
||||
'assoc_handle', 'trust_root', 'openid');
|
||||
|
||||
// Global namespace / alias registration map. See
|
||||
// Auth_OpenID_registerNamespaceAlias.
|
||||
global $Auth_OpenID_registered_aliases;
|
||||
$Auth_OpenID_registered_aliases = array();
|
||||
|
||||
/**
|
||||
* Registers a (namespace URI, alias) mapping in a global namespace
|
||||
* alias map. Raises NamespaceAliasRegistrationError if either the
|
||||
* namespace URI or alias has already been registered with a different
|
||||
* value. This function is required if you want to use a namespace
|
||||
* with an OpenID 1 message.
|
||||
*/
|
||||
function Auth_OpenID_registerNamespaceAlias($namespace_uri, $alias)
|
||||
{
|
||||
global $Auth_OpenID_registered_aliases;
|
||||
|
||||
if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
|
||||
$alias) == $namespace_uri) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($namespace_uri,
|
||||
array_values($Auth_OpenID_registered_aliases))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($alias, array_keys($Auth_OpenID_registered_aliases))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$Auth_OpenID_registered_aliases[$alias] = $namespace_uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a (namespace_uri, alias) registration from the global
|
||||
* namespace alias map. Returns true if the removal succeeded; false
|
||||
* if not (if the mapping did not exist).
|
||||
*/
|
||||
function Auth_OpenID_removeNamespaceAlias($namespace_uri, $alias)
|
||||
{
|
||||
global $Auth_OpenID_registered_aliases;
|
||||
|
||||
if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
|
||||
$alias) === $namespace_uri) {
|
||||
unset($Auth_OpenID_registered_aliases[$alias]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An Auth_OpenID_Mapping maintains a mapping from arbitrary keys to
|
||||
* arbitrary values. (This is unlike an ordinary PHP array, whose
|
||||
* keys may be only simple scalars.)
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_Mapping {
|
||||
/**
|
||||
* Initialize a mapping. If $classic_array is specified, its keys
|
||||
* and values are used to populate the mapping.
|
||||
*/
|
||||
function Auth_OpenID_Mapping($classic_array = null)
|
||||
{
|
||||
$this->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 = "<form accept-charset=\"UTF-8\" ".
|
||||
"enctype=\"application/x-www-form-urlencoded\"";
|
||||
|
||||
if (!$form_tag_attrs) {
|
||||
$form_tag_attrs = array();
|
||||
}
|
||||
|
||||
$form_tag_attrs['action'] = $action_url;
|
||||
$form_tag_attrs['method'] = 'post';
|
||||
|
||||
unset($form_tag_attrs['enctype']);
|
||||
unset($form_tag_attrs['accept-charset']);
|
||||
|
||||
if ($form_tag_attrs) {
|
||||
foreach ($form_tag_attrs as $name => $attr) {
|
||||
$form .= sprintf(" %s=\"%s\"", $name, $attr);
|
||||
}
|
||||
}
|
||||
|
||||
$form .= ">\n";
|
||||
|
||||
foreach ($this->toPostArgs() as $name => $value) {
|
||||
$form .= sprintf(
|
||||
"<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
|
||||
$name, $value);
|
||||
}
|
||||
|
||||
$form .= sprintf("<input type=\"submit\" value=\"%s\" />\n",
|
||||
$submit_text);
|
||||
|
||||
$form .= "</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);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
78
extlib/Auth/OpenID/MySQLStore.php
Normal file
78
extlib/Auth/OpenID/MySQLStore.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A MySQL store.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the base class file.
|
||||
*/
|
||||
require_once "Auth/OpenID/SQLStore.php";
|
||||
|
||||
/**
|
||||
* An SQL store that uses MySQL as its backend.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function setSQL()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
109
extlib/Auth/OpenID/Nonce.php
Normal file
109
extlib/Auth/OpenID/Nonce.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Nonce-related functionality.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Need CryptUtil to generate random strings.
|
||||
*/
|
||||
require_once 'Auth/OpenID/CryptUtil.php';
|
||||
|
||||
/**
|
||||
* This is the characters that the nonces are made from.
|
||||
*/
|
||||
define('Auth_OpenID_Nonce_CHRS',"abcdefghijklmnopqrstuvwxyz" .
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
|
||||
|
||||
// Keep nonces for five hours (allow five hours for the combination of
|
||||
// request time and clock skew). This is probably way more than is
|
||||
// necessary, but there is not much overhead in storing nonces.
|
||||
global $Auth_OpenID_SKEW;
|
||||
$Auth_OpenID_SKEW = 60 * 60 * 5;
|
||||
|
||||
define('Auth_OpenID_Nonce_REGEX',
|
||||
'/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/');
|
||||
|
||||
define('Auth_OpenID_Nonce_TIME_FMT',
|
||||
'%Y-%m-%dT%H:%M:%SZ');
|
||||
|
||||
function Auth_OpenID_splitNonce($nonce_string)
|
||||
{
|
||||
// Extract a timestamp from the given nonce string
|
||||
$result = preg_match(Auth_OpenID_Nonce_REGEX, $nonce_string, $matches);
|
||||
if ($result != 1 || count($matches) != 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($unused,
|
||||
$tm_year,
|
||||
$tm_mon,
|
||||
$tm_mday,
|
||||
$tm_hour,
|
||||
$tm_min,
|
||||
$tm_sec,
|
||||
$uniquifier) = $matches;
|
||||
|
||||
$timestamp =
|
||||
@gmmktime($tm_hour, $tm_min, $tm_sec, $tm_mon, $tm_mday, $tm_year);
|
||||
|
||||
if ($timestamp === false || $timestamp < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array($timestamp, $uniquifier);
|
||||
}
|
||||
|
||||
function Auth_OpenID_checkTimestamp($nonce_string,
|
||||
$allowed_skew = null,
|
||||
$now = null)
|
||||
{
|
||||
// Is the timestamp that is part of the specified nonce string
|
||||
// within the allowed clock-skew of the current time?
|
||||
global $Auth_OpenID_SKEW;
|
||||
|
||||
if ($allowed_skew === null) {
|
||||
$allowed_skew = $Auth_OpenID_SKEW;
|
||||
}
|
||||
|
||||
$parts = Auth_OpenID_splitNonce($nonce_string);
|
||||
if ($parts == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($now === null) {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
$stamp = $parts[0];
|
||||
|
||||
// Time after which we should not use the nonce
|
||||
$past = $now - $allowed_skew;
|
||||
|
||||
// Time that is too far in the future for us to allow
|
||||
$future = $now + $allowed_skew;
|
||||
|
||||
// the stamp is not too far in the future and is not too far
|
||||
// in the past
|
||||
return (($past <= $stamp) && ($stamp <= $future));
|
||||
}
|
||||
|
||||
function Auth_OpenID_mkNonce($when = null)
|
||||
{
|
||||
// Generate a nonce with the current timestamp
|
||||
$salt = Auth_OpenID_CryptUtil::randomString(
|
||||
6, Auth_OpenID_Nonce_CHRS);
|
||||
if ($when === null) {
|
||||
// It's safe to call time() with no arguments; it returns a
|
||||
// GMT unix timestamp on PHP 4 and PHP 5. gmmktime() with no
|
||||
// args returns a local unix timestamp on PHP 4, so don't use
|
||||
// that.
|
||||
$when = time();
|
||||
}
|
||||
$time_str = gmstrftime(Auth_OpenID_Nonce_TIME_FMT, $when);
|
||||
return $time_str . $salt;
|
||||
}
|
||||
|
||||
?>
|
301
extlib/Auth/OpenID/PAPE.php
Normal file
301
extlib/Auth/OpenID/PAPE.php
Normal file
@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* An implementation of the OpenID Provider Authentication Policy
|
||||
* Extension 1.0
|
||||
*
|
||||
* See:
|
||||
* http://openid.net/developers/specs/
|
||||
*/
|
||||
|
||||
require_once "Auth/OpenID/Extension.php";
|
||||
|
||||
define('Auth_OpenID_PAPE_NS_URI',
|
||||
"http://specs.openid.net/extensions/pape/1.0");
|
||||
|
||||
define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
|
||||
'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
|
||||
define('PAPE_AUTH_MULTI_FACTOR',
|
||||
'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
|
||||
define('PAPE_AUTH_PHISHING_RESISTANT',
|
||||
'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');
|
||||
|
||||
define('PAPE_TIME_VALIDATOR',
|
||||
'^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$');
|
||||
/**
|
||||
* A Provider Authentication Policy request, sent from a relying party
|
||||
* to a provider
|
||||
*
|
||||
* preferred_auth_policies: The authentication policies that
|
||||
* the relying party prefers
|
||||
*
|
||||
* max_auth_age: The maximum time, in seconds, that the relying party
|
||||
* wants to allow to have elapsed before the user must re-authenticate
|
||||
*/
|
||||
class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {
|
||||
|
||||
var $ns_alias = 'pape';
|
||||
var $ns_uri = Auth_OpenID_PAPE_NS_URI;
|
||||
|
||||
function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
|
||||
$max_auth_age=null)
|
||||
{
|
||||
if ($preferred_auth_policies === null) {
|
||||
$preferred_auth_policies = array();
|
||||
}
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
352
extlib/Auth/OpenID/Parse.php
Normal file
352
extlib/Auth/OpenID/Parse.php
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module implements a VERY limited parser that finds <link> 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 <html> tag
|
||||
*
|
||||
* - There must be an open <head> tag inside of the <html> tag
|
||||
*
|
||||
* - Only <link>s that are found inside of the <head> 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 <![CDATA[blocks]]>. Both kinds
|
||||
* of quoting are allowed for attributes.
|
||||
*
|
||||
* The parser deals with invalid markup in these ways:
|
||||
*
|
||||
* - Tag names are not case-sensitive
|
||||
*
|
||||
* - The <html> tag is accepted even when it is not at the top level
|
||||
*
|
||||
* - The <head> tag is accepted even when it is not a direct child of
|
||||
* the <html> tag, but a <html> tag must be an ancestor of the
|
||||
* <head> tag
|
||||
*
|
||||
* - <link> tags are accepted even when they are not direct children
|
||||
* of the <head> tag, but a <head> tag must be an ancestor of the
|
||||
* <link> tag
|
||||
*
|
||||
* - If there is no closing tag for an open <html> or <head> tag, the
|
||||
* remainder of the document is viewed as being inside of the
|
||||
* tag. If there is no closing tag for a <link> tag, the link tag is
|
||||
* treated as a short tag. Exceptions to this rule are that <html>
|
||||
* closes <html> and <body> or <head> closes <head>
|
||||
*
|
||||
* - Attributes of the <link> 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. <link pumpkin rel='openid.server' /> will
|
||||
* ignore pumpkin)
|
||||
*
|
||||
* - If there are more than one <html> or <head> tag, the parser only
|
||||
* looks inside of the first one.
|
||||
*
|
||||
* - The contents of <script> tags are ignored entirely, except
|
||||
* unclosed <script> tags. Unclosed <script> tags are ignored.
|
||||
*
|
||||
* - Any other invalid markup is ignored, including unclosed SGML
|
||||
* comments and unclosed <![CDATA[blocks.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require Auth_OpenID::arrayGet().
|
||||
*/
|
||||
require_once "Auth/OpenID.php";
|
||||
|
||||
class Auth_OpenID_Parse {
|
||||
|
||||
/**
|
||||
* Specify some flags for use with regex matching.
|
||||
*/
|
||||
var $_re_flags = "si";
|
||||
|
||||
/**
|
||||
* Stuff to remove before we start looking for tags
|
||||
*/
|
||||
var $_removed_re =
|
||||
"<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
|
||||
|
||||
/**
|
||||
* Starts with the tag name at a word boundary, where the tag name
|
||||
* is not a namespace
|
||||
*/
|
||||
var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*?)(?:<\/?%s\s*>|\Z))";
|
||||
|
||||
var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
|
||||
|
||||
var $_open_tag_expr = "<%s\b";
|
||||
var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";
|
||||
|
||||
function Auth_OpenID_Parse()
|
||||
{
|
||||
$this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
|
||||
$this->_re_flags);
|
||||
|
||||
$this->_entity_replacements = array(
|
||||
'amp' => '&',
|
||||
'lt' => '<',
|
||||
'gt' => '>',
|
||||
'quot' => '"'
|
||||
);
|
||||
|
||||
$this->_attr_find = sprintf("/%s/%s",
|
||||
$this->_attr_find,
|
||||
$this->_re_flags);
|
||||
|
||||
$this->_removed_re = sprintf("/%s/%s",
|
||||
$this->_removed_re,
|
||||
$this->_re_flags);
|
||||
|
||||
$this->_ent_replace =
|
||||
sprintf("&(%s);", implode("|",
|
||||
$this->_entity_replacements));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a regular expression that will match a given tag in an
|
||||
* SGML string.
|
||||
*/
|
||||
function tagMatcher($tag_name, $close_tags = null)
|
||||
{
|
||||
$expr = $this->_tag_expr;
|
||||
|
||||
if ($close_tags) {
|
||||
$options = implode("|", array_merge(array($tag_name), $close_tags));
|
||||
$closer = sprintf("(?:%s)", $options);
|
||||
} else {
|
||||
$closer = $tag_name;
|
||||
}
|
||||
|
||||
$expr = sprintf($expr, $tag_name, $closer);
|
||||
return sprintf("/%s/%s", $expr, $this->_re_flags);
|
||||
}
|
||||
|
||||
function openTag($tag_name)
|
||||
{
|
||||
$expr = sprintf($this->_open_tag_expr, $tag_name);
|
||||
return sprintf("/%s/%s", $expr, $this->_re_flags);
|
||||
}
|
||||
|
||||
function closeTag($tag_name)
|
||||
{
|
||||
$expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
|
||||
return sprintf("/%s/%s", $expr, $this->_re_flags);
|
||||
}
|
||||
|
||||
function htmlBegin($s)
|
||||
{
|
||||
$matches = array();
|
||||
$result = preg_match($this->openTag('html'), $s,
|
||||
$matches, PREG_OFFSET_CAPTURE);
|
||||
if ($result === false || !$matches) {
|
||||
return false;
|
||||
}
|
||||
// Return the offset of the first match.
|
||||
return $matches[0][1];
|
||||
}
|
||||
|
||||
function htmlEnd($s)
|
||||
{
|
||||
$matches = array();
|
||||
$result = preg_match($this->closeTag('html'), $s,
|
||||
$matches, PREG_OFFSET_CAPTURE);
|
||||
if ($result === false || !$matches) {
|
||||
return false;
|
||||
}
|
||||
// Return the offset of the first match.
|
||||
return $matches[count($matches) - 1][1];
|
||||
}
|
||||
|
||||
function headFind()
|
||||
{
|
||||
return $this->tagMatcher('head', array('body', 'html'));
|
||||
}
|
||||
|
||||
function replaceEntities($str)
|
||||
{
|
||||
foreach ($this->_entity_replacements as $old => $new) {
|
||||
$str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
function removeQuotes($str)
|
||||
{
|
||||
$matches = array();
|
||||
$double = '/^"(.*)"$/';
|
||||
$single = "/^\'(.*)\'$/";
|
||||
|
||||
if (preg_match($double, $str, $matches)) {
|
||||
return $matches[1];
|
||||
} else if (preg_match($single, $str, $matches)) {
|
||||
return $matches[1];
|
||||
} else {
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all link tags in a string representing a HTML document and
|
||||
* return a list of their attributes.
|
||||
*
|
||||
* @param string $html The text to parse
|
||||
* @return array $list An array of arrays of attributes, one for each
|
||||
* link tag
|
||||
*/
|
||||
function parseLinkAttrs($html)
|
||||
{
|
||||
$stripped = preg_replace($this->_removed_re,
|
||||
"",
|
||||
$html);
|
||||
|
||||
$html_begin = $this->htmlBegin($stripped);
|
||||
$html_end = $this->htmlEnd($stripped);
|
||||
|
||||
if ($html_begin === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ($html_end === false) {
|
||||
$html_end = strlen($stripped);
|
||||
}
|
||||
|
||||
$stripped = substr($stripped, $html_begin,
|
||||
$html_end - $html_begin);
|
||||
|
||||
// Try to find the <HEAD> tag.
|
||||
$head_re = $this->headFind();
|
||||
$head_matches = array();
|
||||
if (!preg_match($head_re, $stripped, $head_matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$link_data = array();
|
||||
$link_matches = array();
|
||||
|
||||
if (!preg_match_all($this->_link_find, $head_matches[0],
|
||||
$link_matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($link_matches[0] as $link) {
|
||||
$attr_matches = array();
|
||||
preg_match_all($this->_attr_find, $link, $attr_matches);
|
||||
$link_attrs = array();
|
||||
foreach ($attr_matches[0] as $index => $full_match) {
|
||||
$name = $attr_matches[1][$index];
|
||||
$value = $this->replaceEntities(
|
||||
$this->removeQuotes($attr_matches[2][$index]));
|
||||
|
||||
$link_attrs[strtolower($name)] = $value;
|
||||
}
|
||||
$link_data[] = $link_attrs;
|
||||
}
|
||||
|
||||
return $link_data;
|
||||
}
|
||||
|
||||
function relMatches($rel_attr, $target_rel)
|
||||
{
|
||||
// Does this target_rel appear in the rel_str?
|
||||
// XXX: TESTME
|
||||
$rels = preg_split("/\s+/", trim($rel_attr));
|
||||
foreach ($rels as $rel) {
|
||||
$rel = strtolower($rel);
|
||||
if ($rel == $target_rel) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function linkHasRel($link_attrs, $target_rel)
|
||||
{
|
||||
// Does this link have target_rel as a relationship?
|
||||
// XXX: TESTME
|
||||
$rel_attr = Auth_OpeniD::arrayGet($link_attrs, 'rel', null);
|
||||
return ($rel_attr && $this->relMatches($rel_attr,
|
||||
$target_rel));
|
||||
}
|
||||
|
||||
function findLinksRel($link_attrs_list, $target_rel)
|
||||
{
|
||||
// Filter the list of link attributes on whether it has
|
||||
// target_rel as a relationship.
|
||||
// XXX: TESTME
|
||||
$result = array();
|
||||
foreach ($link_attrs_list as $attr) {
|
||||
if ($this->linkHasRel($attr, $target_rel)) {
|
||||
$result[] = $attr;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function findFirstHref($link_attrs_list, $target_rel)
|
||||
{
|
||||
// Return the value of the href attribute for the first link
|
||||
// tag in the list that has target_rel as a relationship.
|
||||
// XXX: TESTME
|
||||
$matches = $this->findLinksRel($link_attrs_list,
|
||||
$target_rel);
|
||||
if (!$matches) {
|
||||
return null;
|
||||
}
|
||||
$first = $matches[0];
|
||||
return Auth_OpenID::arrayGet($first, 'href', null);
|
||||
}
|
||||
}
|
||||
|
||||
function Auth_OpenID_legacy_discover($html_text, $server_rel,
|
||||
$delegate_rel)
|
||||
{
|
||||
$p = new Auth_OpenID_Parse();
|
||||
|
||||
$link_attrs = $p->parseLinkAttrs($html_text);
|
||||
|
||||
$server_url = $p->findFirstHref($link_attrs,
|
||||
$server_rel);
|
||||
|
||||
if ($server_url === null) {
|
||||
return false;
|
||||
} else {
|
||||
$delegate_url = $p->findFirstHref($link_attrs,
|
||||
$delegate_rel);
|
||||
return array($delegate_url, $server_url);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
113
extlib/Auth/OpenID/PostgreSQLStore.php
Normal file
113
extlib/Auth/OpenID/PostgreSQLStore.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A PostgreSQL store.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the base class file.
|
||||
*/
|
||||
require_once "Auth/OpenID/SQLStore.php";
|
||||
|
||||
/**
|
||||
* An SQL store that uses PostgreSQL as its backend.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function setSQL()
|
||||
{
|
||||
$this->sql['nonce_table'] =
|
||||
"CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
|
||||
"timestamp INTEGER NOT NULL, ".
|
||||
"salt CHAR(40) NOT NULL, ".
|
||||
"UNIQUE (server_url, timestamp, salt))";
|
||||
|
||||
$this->sql['assoc_table'] =
|
||||
"CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
|
||||
"handle VARCHAR(255) NOT NULL, ".
|
||||
"secret BYTEA NOT NULL, ".
|
||||
"issued INTEGER NOT NULL, ".
|
||||
"lifetime INTEGER NOT NULL, ".
|
||||
"assoc_type VARCHAR(64) NOT NULL, ".
|
||||
"PRIMARY KEY (server_url, handle), ".
|
||||
"CONSTRAINT secret_length_constraint CHECK ".
|
||||
"(LENGTH(secret) <= 128))";
|
||||
|
||||
$this->sql['set_assoc'] =
|
||||
array(
|
||||
'insert_assoc' => "INSERT INTO %s (server_url, handle, ".
|
||||
"secret, issued, lifetime, assoc_type) VALUES ".
|
||||
"(?, ?, '!', ?, ?, ?)",
|
||||
'update_assoc' => "UPDATE %s SET secret = '!', issued = ?, ".
|
||||
"lifetime = ?, assoc_type = ? WHERE server_url = ? AND ".
|
||||
"handle = ?"
|
||||
);
|
||||
|
||||
$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 _set_assoc($server_url, $handle, $secret, $issued, $lifetime,
|
||||
$assoc_type)
|
||||
{
|
||||
$result = $this->_get_assoc($server_url, $handle);
|
||||
if ($result) {
|
||||
// Update the table since this associations already exists.
|
||||
$this->connection->query($this->sql['set_assoc']['update_assoc'],
|
||||
array($secret, $issued, $lifetime,
|
||||
$assoc_type, $server_url, $handle));
|
||||
} else {
|
||||
// Insert a new record because this association wasn't
|
||||
// found.
|
||||
$this->connection->query($this->sql['set_assoc']['insert_assoc'],
|
||||
array($server_url, $handle, $secret,
|
||||
$issued, $lifetime, $assoc_type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function blobEncode($blob)
|
||||
{
|
||||
return $this->_octify($blob);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function blobDecode($blob)
|
||||
{
|
||||
return $this->_unoctify($blob);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
569
extlib/Auth/OpenID/SQLStore.php
Normal file
569
extlib/Auth/OpenID/SQLStore.php
Normal file
@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SQL-backed OpenID stores.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the PEAR DB module because we'll need it for the SQL-based
|
||||
* stores implemented here. We silence any errors from the inclusion
|
||||
* because it might not be present, and a user of the SQL stores may
|
||||
* supply an Auth_OpenID_DatabaseConnection instance that implements
|
||||
* its own storage.
|
||||
*/
|
||||
global $__Auth_OpenID_PEAR_AVAILABLE;
|
||||
$__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php';
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
require_once 'Auth/OpenID/Interface.php';
|
||||
require_once 'Auth/OpenID/Nonce.php';
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
require_once 'Auth/OpenID.php';
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
require_once 'Auth/OpenID/Nonce.php';
|
||||
|
||||
/**
|
||||
* This is the parent class for the SQL stores, which contains the
|
||||
* logic common to all of the SQL stores.
|
||||
*
|
||||
* The table names used are determined by the class variables
|
||||
* associations_table_name and nonces_table_name. To change the name
|
||||
* of the tables used, pass new table names into the constructor.
|
||||
*
|
||||
* To create the tables with the proper schema, see the createTables
|
||||
* method.
|
||||
*
|
||||
* This class shouldn't be used directly. Use one of its subclasses
|
||||
* instead, as those contain the code necessary to use a specific
|
||||
* database. If you're an OpenID integrator and you'd like to create
|
||||
* an SQL-driven store that wraps an application's database
|
||||
* abstraction, be sure to create a subclass of
|
||||
* {@link Auth_OpenID_DatabaseConnection} that calls the application's
|
||||
* database abstraction calls. Then, pass an instance of your new
|
||||
* database connection class to your SQLStore subclass constructor.
|
||||
*
|
||||
* All methods other than the constructor and createTables should be
|
||||
* considered implementation details.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
|
||||
|
||||
/**
|
||||
* This creates a new SQLStore instance. It requires an
|
||||
* established database connection be given to it, and it allows
|
||||
* overriding the default table names.
|
||||
*
|
||||
* @param connection $connection This must be an established
|
||||
* connection to a database of the correct type for the SQLStore
|
||||
* subclass you're using. This must either be an PEAR DB
|
||||
* connection handle or an instance of a subclass of
|
||||
* Auth_OpenID_DatabaseConnection.
|
||||
*
|
||||
* @param associations_table: This is an optional parameter to
|
||||
* specify the name of the table used for storing associations.
|
||||
* The default value is 'oid_associations'.
|
||||
*
|
||||
* @param nonces_table: This is an optional parameter to specify
|
||||
* the name of the table used for storing nonces. The default
|
||||
* value is 'oid_nonces'.
|
||||
*/
|
||||
function Auth_OpenID_SQLStore($connection,
|
||||
$associations_table = null,
|
||||
$nonces_table = null)
|
||||
{
|
||||
global $__Auth_OpenID_PEAR_AVAILABLE;
|
||||
|
||||
$this->associations_table_name = "oid_associations";
|
||||
$this->nonces_table_name = "oid_nonces";
|
||||
|
||||
// Check the connection object type to be sure it's a PEAR
|
||||
// database connection.
|
||||
if (!(is_object($connection) &&
|
||||
(is_subclass_of($connection, 'db_common') ||
|
||||
is_subclass_of($connection,
|
||||
'auth_openid_databaseconnection')))) {
|
||||
trigger_error("Auth_OpenID_SQLStore expected PEAR connection " .
|
||||
"object (got ".get_class($connection).")",
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->connection = $connection;
|
||||
|
||||
// Be sure to set the fetch mode so the results are keyed on
|
||||
// column name instead of column index. This is a PEAR
|
||||
// constant, so only try to use it if PEAR is present. Note
|
||||
// that Auth_Openid_Databaseconnection instances need not
|
||||
// implement ::setFetchMode for this reason.
|
||||
if ($__Auth_OpenID_PEAR_AVAILABLE) {
|
||||
$this->connection->setFetchMode(DB_FETCHMODE_ASSOC);
|
||||
}
|
||||
|
||||
if ($associations_table) {
|
||||
$this->associations_table_name = $associations_table;
|
||||
}
|
||||
|
||||
if ($nonces_table) {
|
||||
$this->nonces_table_name = $nonces_table;
|
||||
}
|
||||
|
||||
$this->max_nonce_age = 6 * 60 * 60;
|
||||
|
||||
// Be sure to run the database queries with auto-commit mode
|
||||
// turned OFF, because we want every function to run in a
|
||||
// transaction, implicitly. As a rule, methods named with a
|
||||
// leading underscore will NOT control transaction behavior.
|
||||
// Callers of these methods will worry about transactions.
|
||||
$this->connection->autoCommit(false);
|
||||
|
||||
// Create an empty SQL strings array.
|
||||
$this->sql = array();
|
||||
|
||||
// Call this method (which should be overridden by subclasses)
|
||||
// to populate the $this->sql array with SQL strings.
|
||||
$this->setSQL();
|
||||
|
||||
// Verify that all required SQL statements have been set, and
|
||||
// raise an error if any expected SQL strings were either
|
||||
// absent or empty.
|
||||
list($missing, $empty) = $this->_verifySQL();
|
||||
|
||||
if ($missing) {
|
||||
trigger_error("Expected keys in SQL query list: " .
|
||||
implode(", ", $missing),
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($empty) {
|
||||
trigger_error("SQL list keys have no SQL strings: " .
|
||||
implode(", ", $empty),
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add table names to queries.
|
||||
$this->_fixSQL();
|
||||
}
|
||||
|
||||
function tableExists($table_name)
|
||||
{
|
||||
return !$this->isError(
|
||||
$this->connection->query(
|
||||
sprintf("SELECT * FROM %s LIMIT 0",
|
||||
$table_name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if $value constitutes a database error; returns
|
||||
* false otherwise.
|
||||
*/
|
||||
function isError($value)
|
||||
{
|
||||
return PEAR::isError($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a query result to a boolean. If the result is a
|
||||
* database error according to $this->isError(), this returns
|
||||
* false; otherwise, this returns true.
|
||||
*/
|
||||
function resultToBool($obj)
|
||||
{
|
||||
if ($this->isError($obj)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be overridden by subclasses. This method is
|
||||
* called by the constructor to set values in $this->sql, which is
|
||||
* an array keyed on sql name.
|
||||
*/
|
||||
function setSQL()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the store by removing all records from the store's
|
||||
* tables.
|
||||
*/
|
||||
function reset()
|
||||
{
|
||||
$this->connection->query(sprintf("DELETE FROM %s",
|
||||
$this->associations_table_name));
|
||||
|
||||
$this->connection->query(sprintf("DELETE FROM %s",
|
||||
$this->nonces_table_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _verifySQL()
|
||||
{
|
||||
$missing = array();
|
||||
$empty = array();
|
||||
|
||||
$required_sql_keys = array(
|
||||
'nonce_table',
|
||||
'assoc_table',
|
||||
'set_assoc',
|
||||
'get_assoc',
|
||||
'get_assocs',
|
||||
'remove_assoc'
|
||||
);
|
||||
|
||||
foreach ($required_sql_keys as $key) {
|
||||
if (!array_key_exists($key, $this->sql)) {
|
||||
$missing[] = $key;
|
||||
} else if (!$this->sql[$key]) {
|
||||
$empty[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return array($missing, $empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _fixSQL()
|
||||
{
|
||||
$replacements = array(
|
||||
array(
|
||||
'value' => $this->nonces_table_name,
|
||||
'keys' => array('nonce_table',
|
||||
'add_nonce',
|
||||
'clean_nonce')
|
||||
),
|
||||
array(
|
||||
'value' => $this->associations_table_name,
|
||||
'keys' => array('assoc_table',
|
||||
'set_assoc',
|
||||
'get_assoc',
|
||||
'get_assocs',
|
||||
'remove_assoc',
|
||||
'clean_assoc')
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($replacements as $item) {
|
||||
$value = $item['value'];
|
||||
$keys = $item['keys'];
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (is_array($this->sql[$k])) {
|
||||
foreach ($this->sql[$k] as $part_key => $part_value) {
|
||||
$this->sql[$k][$part_key] = sprintf($part_value,
|
||||
$value);
|
||||
}
|
||||
} else {
|
||||
$this->sql[$k] = sprintf($this->sql[$k], $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function blobDecode($blob)
|
||||
{
|
||||
return $blob;
|
||||
}
|
||||
|
||||
function blobEncode($str)
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
function createTables()
|
||||
{
|
||||
$this->connection->autoCommit(true);
|
||||
$n = $this->create_nonce_table();
|
||||
$a = $this->create_assoc_table();
|
||||
$this->connection->autoCommit(false);
|
||||
|
||||
if ($n && $a) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function create_nonce_table()
|
||||
{
|
||||
if (!$this->tableExists($this->nonces_table_name)) {
|
||||
$r = $this->connection->query($this->sql['nonce_table']);
|
||||
return $this->resultToBool($r);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function create_assoc_table()
|
||||
{
|
||||
if (!$this->tableExists($this->associations_table_name)) {
|
||||
$r = $this->connection->query($this->sql['assoc_table']);
|
||||
return $this->resultToBool($r);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _set_assoc($server_url, $handle, $secret, $issued,
|
||||
$lifetime, $assoc_type)
|
||||
{
|
||||
return $this->connection->query($this->sql['set_assoc'],
|
||||
array(
|
||||
$server_url,
|
||||
$handle,
|
||||
$secret,
|
||||
$issued,
|
||||
$lifetime,
|
||||
$assoc_type));
|
||||
}
|
||||
|
||||
function storeAssociation($server_url, $association)
|
||||
{
|
||||
if ($this->resultToBool($this->_set_assoc(
|
||||
$server_url,
|
||||
$association->handle,
|
||||
$this->blobEncode(
|
||||
$association->secret),
|
||||
$association->issued,
|
||||
$association->lifetime,
|
||||
$association->assoc_type
|
||||
))) {
|
||||
$this->connection->commit();
|
||||
} else {
|
||||
$this->connection->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _get_assoc($server_url, $handle)
|
||||
{
|
||||
$result = $this->connection->getRow($this->sql['get_assoc'],
|
||||
array($server_url, $handle));
|
||||
if ($this->isError($result)) {
|
||||
return null;
|
||||
} else {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _get_assocs($server_url)
|
||||
{
|
||||
$result = $this->connection->getAll($this->sql['get_assocs'],
|
||||
array($server_url));
|
||||
|
||||
if ($this->isError($result)) {
|
||||
return array();
|
||||
} else {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
function removeAssociation($server_url, $handle)
|
||||
{
|
||||
if ($this->_get_assoc($server_url, $handle) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->resultToBool($this->connection->query(
|
||||
$this->sql['remove_assoc'],
|
||||
array($server_url, $handle)))) {
|
||||
$this->connection->commit();
|
||||
} else {
|
||||
$this->connection->rollback();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getAssociation($server_url, $handle = null)
|
||||
{
|
||||
if ($handle !== null) {
|
||||
$assoc = $this->_get_assoc($server_url, $handle);
|
||||
|
||||
$assocs = array();
|
||||
if ($assoc) {
|
||||
$assocs[] = $assoc;
|
||||
}
|
||||
} else {
|
||||
$assocs = $this->_get_assocs($server_url);
|
||||
}
|
||||
|
||||
if (!$assocs || (count($assocs) == 0)) {
|
||||
return null;
|
||||
} else {
|
||||
$associations = array();
|
||||
|
||||
foreach ($assocs as $assoc_row) {
|
||||
$assoc = new Auth_OpenID_Association($assoc_row['handle'],
|
||||
$assoc_row['secret'],
|
||||
$assoc_row['issued'],
|
||||
$assoc_row['lifetime'],
|
||||
$assoc_row['assoc_type']);
|
||||
|
||||
$assoc->secret = $this->blobDecode($assoc->secret);
|
||||
|
||||
if ($assoc->getExpiresIn() == 0) {
|
||||
$this->removeAssociation($server_url, $assoc->handle);
|
||||
} else {
|
||||
$associations[] = array($assoc->issued, $assoc);
|
||||
}
|
||||
}
|
||||
|
||||
if ($associations) {
|
||||
$issued = array();
|
||||
$assocs = array();
|
||||
foreach ($associations as $key => $assoc) {
|
||||
$issued[$key] = $assoc[0];
|
||||
$assocs[$key] = $assoc[1];
|
||||
}
|
||||
|
||||
array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
|
||||
$associations);
|
||||
|
||||
// return the most recently issued one.
|
||||
list($issued, $assoc) = $associations[0];
|
||||
return $assoc;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _add_nonce($server_url, $timestamp, $salt)
|
||||
{
|
||||
$sql = $this->sql['add_nonce'];
|
||||
$result = $this->connection->query($sql, array($server_url,
|
||||
$timestamp,
|
||||
$salt));
|
||||
if ($this->isError($result)) {
|
||||
$this->connection->rollback();
|
||||
} else {
|
||||
$this->connection->commit();
|
||||
}
|
||||
return $this->resultToBool($result);
|
||||
}
|
||||
|
||||
function useNonce($server_url, $timestamp, $salt)
|
||||
{
|
||||
global $Auth_OpenID_SKEW;
|
||||
|
||||
if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
|
||||
return False;
|
||||
}
|
||||
|
||||
return $this->_add_nonce($server_url, $timestamp, $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Octifies" a binary string by returning a string with escaped
|
||||
* octal bytes. This is used for preparing binary data for
|
||||
* PostgreSQL BYTEA fields.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _octify($str)
|
||||
{
|
||||
$result = "";
|
||||
for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) {
|
||||
$ch = substr($str, $i, 1);
|
||||
if ($ch == "\\") {
|
||||
$result .= "\\\\\\\\";
|
||||
} else if (ord($ch) == 0) {
|
||||
$result .= "\\\\000";
|
||||
} else {
|
||||
$result .= "\\" . strval(decoct(ord($ch)));
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Unoctifies" octal-escaped data from PostgreSQL and returns the
|
||||
* resulting ASCII (possibly binary) string.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _unoctify($str)
|
||||
{
|
||||
$result = "";
|
||||
$i = 0;
|
||||
while ($i < strlen($str)) {
|
||||
$char = $str[$i];
|
||||
if ($char == "\\") {
|
||||
// Look to see if the next char is a backslash and
|
||||
// append it.
|
||||
if ($str[$i + 1] != "\\") {
|
||||
$octal_digits = substr($str, $i + 1, 3);
|
||||
$dec = octdec($octal_digits);
|
||||
$char = chr($dec);
|
||||
$i += 4;
|
||||
} else {
|
||||
$char = "\\";
|
||||
$i += 2;
|
||||
}
|
||||
} else {
|
||||
$i += 1;
|
||||
}
|
||||
|
||||
$result .= $char;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function cleanupNonces()
|
||||
{
|
||||
global $Auth_OpenID_SKEW;
|
||||
$v = time() - $Auth_OpenID_SKEW;
|
||||
|
||||
$this->connection->query($this->sql['clean_nonce'], array($v));
|
||||
$num = $this->connection->affectedRows();
|
||||
$this->connection->commit();
|
||||
return $num;
|
||||
}
|
||||
|
||||
function cleanupAssociations()
|
||||
{
|
||||
$this->connection->query($this->sql['clean_assoc'],
|
||||
array(time()));
|
||||
$num = $this->connection->affectedRows();
|
||||
$this->connection->commit();
|
||||
return $num;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
71
extlib/Auth/OpenID/SQLiteStore.php
Normal file
71
extlib/Auth/OpenID/SQLiteStore.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* An SQLite store.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the base class file.
|
||||
*/
|
||||
require_once "Auth/OpenID/SQLStore.php";
|
||||
|
||||
/**
|
||||
* An SQL store that uses SQLite as its backend.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
|
||||
function setSQL()
|
||||
{
|
||||
$this->sql['nonce_table'] =
|
||||
"CREATE TABLE %s (server_url VARCHAR(2047), timestamp INTEGER, ".
|
||||
"salt CHAR(40), UNIQUE (server_url, timestamp, salt))";
|
||||
|
||||
$this->sql['assoc_table'] =
|
||||
"CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
|
||||
"secret BLOB(128), issued INTEGER, lifetime INTEGER, ".
|
||||
"assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle))";
|
||||
|
||||
$this->sql['set_assoc'] =
|
||||
"INSERT OR REPLACE INTO %s 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 _add_nonce($server_url, $timestamp, $salt)
|
||||
{
|
||||
// PECL SQLite extensions 1.0.3 and older (1.0.3 is the
|
||||
// current release at the time of this writing) have a broken
|
||||
// sqlite_escape_string function that breaks when passed the
|
||||
// empty string. Prefixing all strings with one character
|
||||
// keeps them unique and avoids this bug. The nonce table is
|
||||
// write-only, so we don't have to worry about updating other
|
||||
// functions with this same bad hack.
|
||||
return parent::_add_nonce('x' . $server_url, $timestamp, $salt);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
521
extlib/Auth/OpenID/SReg.php
Normal file
521
extlib/Auth/OpenID/SReg.php
Normal file
@ -0,0 +1,521 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Simple registration request and response parsing and object
|
||||
* representation.
|
||||
*
|
||||
* This module contains objects representing simple registration
|
||||
* requests and responses that can be used with both OpenID relying
|
||||
* parties and OpenID providers.
|
||||
*
|
||||
* 1. The relying party creates a request object and adds it to the
|
||||
* {@link Auth_OpenID_AuthRequest} object before making the
|
||||
* checkid request to the OpenID provider:
|
||||
*
|
||||
* $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
|
||||
* $auth_request->addExtension($sreg_req);
|
||||
*
|
||||
* 2. The OpenID provider extracts the simple registration request
|
||||
* from the OpenID request using {@link
|
||||
* Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
|
||||
* approval and data, creates an {@link Auth_OpenID_SRegResponse}
|
||||
* object and adds it to the id_res response:
|
||||
*
|
||||
* $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
|
||||
* $checkid_request);
|
||||
* // [ get the user's approval and data, informing the user that
|
||||
* // the fields in sreg_response were requested ]
|
||||
* $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
|
||||
* $sreg_req, $user_data);
|
||||
* $sreg_resp->toMessage($openid_response->fields);
|
||||
*
|
||||
* 3. The relying party uses {@link
|
||||
* Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
|
||||
* from the OpenID response:
|
||||
*
|
||||
* $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
|
||||
* $success_response);
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Import message and extension internals.
|
||||
*/
|
||||
require_once 'Auth/OpenID/Message.php';
|
||||
require_once 'Auth/OpenID/Extension.php';
|
||||
|
||||
// The data fields that are listed in the sreg spec
|
||||
global $Auth_OpenID_sreg_data_fields;
|
||||
$Auth_OpenID_sreg_data_fields = array(
|
||||
'fullname' => 'Full Name',
|
||||
'nickname' => 'Nickname',
|
||||
'dob' => 'Date of Birth',
|
||||
'email' => 'E-mail Address',
|
||||
'gender' => 'Gender',
|
||||
'postcode' => 'Postal Code',
|
||||
'country' => 'Country',
|
||||
'language' => 'Language',
|
||||
'timezone' => 'Time Zone');
|
||||
|
||||
/**
|
||||
* Check to see that the given value is a valid simple registration
|
||||
* data field name. Return true if so, false if not.
|
||||
*/
|
||||
function Auth_OpenID_checkFieldName($field_name)
|
||||
{
|
||||
global $Auth_OpenID_sreg_data_fields;
|
||||
|
||||
if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// URI used in the wild for Yadis documents advertising simple
|
||||
// registration support
|
||||
define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');
|
||||
|
||||
// URI in the draft specification for simple registration 1.1
|
||||
// <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
|
||||
define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');
|
||||
|
||||
// This attribute will always hold the preferred URI to use when
|
||||
// adding sreg support to an XRDS file or in an OpenID namespace
|
||||
// declaration.
|
||||
define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);
|
||||
|
||||
Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');
|
||||
|
||||
/**
|
||||
* Does the given endpoint advertise support for simple
|
||||
* registration?
|
||||
*
|
||||
* $endpoint: The endpoint object as returned by OpenID discovery.
|
||||
* returns whether an sreg type was advertised by the endpoint
|
||||
*/
|
||||
function Auth_OpenID_supportsSReg(&$endpoint)
|
||||
{
|
||||
return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
|
||||
$endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for classes dealing with Simple Registration protocol
|
||||
* messages.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
|
||||
/**
|
||||
* Extract the simple registration namespace URI from the given
|
||||
* OpenID message. Handles OpenID 1 and 2, as well as both sreg
|
||||
* namespace URIs found in the wild, as well as missing namespace
|
||||
* definitions (for OpenID 1)
|
||||
*
|
||||
* $message: The OpenID message from which to parse simple
|
||||
* registration fields. This may be a request or response message.
|
||||
*
|
||||
* Returns the sreg namespace URI for the supplied message. The
|
||||
* message may be modified to define a simple registration
|
||||
* namespace.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _getSRegNS(&$message)
|
||||
{
|
||||
$alias = null;
|
||||
$found_ns_uri = null;
|
||||
|
||||
// See if there exists an alias for one of the two defined
|
||||
// simple registration types.
|
||||
foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
|
||||
Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
|
||||
$alias = $message->namespaces->getAlias($sreg_ns_uri);
|
||||
if ($alias !== null) {
|
||||
$found_ns_uri = $sreg_ns_uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($alias === null) {
|
||||
// There is no alias for either of the types, so try to
|
||||
// add one. We default to using the modern value (1.1)
|
||||
$found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
|
||||
if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
|
||||
'sreg') === null) {
|
||||
// An alias for the string 'sreg' already exists, but
|
||||
// it's defined for something other than simple
|
||||
// registration
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $found_ns_uri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to hold the state of a simple registration request.
|
||||
*
|
||||
* required: A list of the required fields in this simple registration
|
||||
* request
|
||||
*
|
||||
* optional: A list of the optional fields in this simple registration
|
||||
* request
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
|
||||
|
||||
var $ns_alias = 'sreg';
|
||||
|
||||
/**
|
||||
* Initialize an empty simple registration request.
|
||||
*/
|
||||
function build($required=null, $optional=null,
|
||||
$policy_url=null,
|
||||
$sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
|
||||
$cls='Auth_OpenID_SRegRequest')
|
||||
{
|
||||
$obj = new $cls();
|
||||
|
||||
$obj->required = array();
|
||||
$obj->optional = array();
|
||||
$obj->policy_url = $policy_url;
|
||||
$obj->ns_uri = $sreg_ns_uri;
|
||||
|
||||
if ($required) {
|
||||
if (!$obj->requestFields($required, true, true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($optional) {
|
||||
if (!$obj->requestFields($optional, false, true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple registration request that contains the fields
|
||||
* that were requested in the OpenID request with the given
|
||||
* arguments
|
||||
*
|
||||
* $request: The OpenID authentication request from which to
|
||||
* extract an sreg request.
|
||||
*
|
||||
* $cls: name of class to use when creating sreg request object.
|
||||
* Used for testing.
|
||||
*
|
||||
* Returns the newly created simple registration request
|
||||
*/
|
||||
function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
|
||||
{
|
||||
|
||||
$obj = call_user_func_array(array($cls, 'build'),
|
||||
array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
|
||||
|
||||
// Since we're going to mess with namespace URI mapping, don't
|
||||
// mutate the object that was passed in.
|
||||
$m = $request->message;
|
||||
|
||||
$obj->ns_uri = $obj->_getSRegNS($m);
|
||||
$args = $m->getArgs($obj->ns_uri);
|
||||
|
||||
if ($args === null || Auth_OpenID::isFailure($args)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$obj->parseExtensionArgs($args);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the unqualified simple registration request parameters
|
||||
* and add them to this object.
|
||||
*
|
||||
* This method is essentially the inverse of
|
||||
* getExtensionArgs. This method restores the serialized simple
|
||||
* registration request fields.
|
||||
*
|
||||
* If you are extracting arguments from a standard OpenID
|
||||
* checkid_* request, you probably want to use fromOpenIDRequest,
|
||||
* which will extract the sreg namespace and arguments from the
|
||||
* OpenID request. This method is intended for cases where the
|
||||
* OpenID server needs more control over how the arguments are
|
||||
* parsed than that method provides.
|
||||
*
|
||||
* $args == $message->getArgs($ns_uri);
|
||||
* $request->parseExtensionArgs($args);
|
||||
*
|
||||
* $args: The unqualified simple registration arguments
|
||||
*
|
||||
* strict: Whether requests with fields that are not defined in
|
||||
* the simple registration specification should be tolerated (and
|
||||
* ignored)
|
||||
*/
|
||||
function parseExtensionArgs($args, $strict=false)
|
||||
{
|
||||
foreach (array('required', 'optional') as $list_name) {
|
||||
$required = ($list_name == 'required');
|
||||
$items = Auth_OpenID::arrayGet($args, $list_name);
|
||||
if ($items) {
|
||||
foreach (explode(',', $items) as $field_name) {
|
||||
if (!$this->requestField($field_name, $required, $strict)) {
|
||||
if ($strict) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all of the simple registration fields that were
|
||||
* requested, whether they were required or optional.
|
||||
*/
|
||||
function allRequestedFields()
|
||||
{
|
||||
return array_merge($this->required, $this->optional);
|
||||
}
|
||||
|
||||
/**
|
||||
* Have any simple registration fields been requested?
|
||||
*/
|
||||
function wereFieldsRequested()
|
||||
{
|
||||
return count($this->allRequestedFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Was this field in the request?
|
||||
*/
|
||||
function contains($field_name)
|
||||
{
|
||||
return (in_array($field_name, $this->required) ||
|
||||
in_array($field_name, $this->optional));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the specified field from the OpenID user
|
||||
*
|
||||
* $field_name: the unqualified simple registration field name
|
||||
*
|
||||
* required: whether the given field should be presented to the
|
||||
* user as being a required to successfully complete the request
|
||||
*
|
||||
* strict: whether to raise an exception when a field is added to
|
||||
* a request more than once
|
||||
*/
|
||||
function requestField($field_name,
|
||||
$required=false, $strict=false)
|
||||
{
|
||||
if (!Auth_OpenID_checkFieldName($field_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($strict) {
|
||||
if ($this->contains($field_name)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (in_array($field_name, $this->required)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($field_name, $this->optional)) {
|
||||
if ($required) {
|
||||
unset($this->optional[array_search($field_name,
|
||||
$this->optional)]);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($required) {
|
||||
$this->required[] = $field_name;
|
||||
} else {
|
||||
$this->optional[] = $field_name;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given list of fields to the request
|
||||
*
|
||||
* field_names: The simple registration data fields to request
|
||||
*
|
||||
* required: Whether these values should be presented to the user
|
||||
* as required
|
||||
*
|
||||
* strict: whether to raise an exception when a field is added to
|
||||
* a request more than once
|
||||
*/
|
||||
function requestFields($field_names, $required=false, $strict=false)
|
||||
{
|
||||
if (!is_array($field_names)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($field_names as $field_name) {
|
||||
if (!$this->requestField($field_name, $required, $strict=$strict)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dictionary of unqualified simple registration arguments
|
||||
* representing this request.
|
||||
*
|
||||
* This method is essentially the inverse of
|
||||
* C{L{parseExtensionArgs}}. This method serializes the simple
|
||||
* registration request fields.
|
||||
*/
|
||||
function getExtensionArgs()
|
||||
{
|
||||
$args = array();
|
||||
|
||||
if ($this->required) {
|
||||
$args['required'] = implode(',', $this->required);
|
||||
}
|
||||
|
||||
if ($this->optional) {
|
||||
$args['optional'] = implode(',', $this->optional);
|
||||
}
|
||||
|
||||
if ($this->policy_url) {
|
||||
$args['policy_url'] = $this->policy_url;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the data returned in a simple registration response
|
||||
* inside of an OpenID C{id_res} response. This object will be created
|
||||
* by the OpenID server, added to the C{id_res} response object, and
|
||||
* then extracted from the C{id_res} message by the Consumer.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
|
||||
|
||||
var $ns_alias = 'sreg';
|
||||
|
||||
function Auth_OpenID_SRegResponse($data=null,
|
||||
$sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
|
||||
{
|
||||
if ($data === null) {
|
||||
$this->data = array();
|
||||
} else {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
$this->ns_uri = $sreg_ns_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a C{L{SRegRequest}} and a dictionary of simple
|
||||
* registration values and create a C{L{SRegResponse}} object
|
||||
* containing that data.
|
||||
*
|
||||
* request: The simple registration request object
|
||||
*
|
||||
* data: The simple registration data for this response, as a
|
||||
* dictionary from unqualified simple registration field name to
|
||||
* string (unicode) value. For instance, the nickname should be
|
||||
* stored under the key 'nickname'.
|
||||
*/
|
||||
function extractResponse($request, $data)
|
||||
{
|
||||
$obj = new Auth_OpenID_SRegResponse();
|
||||
$obj->ns_uri = $request->ns_uri;
|
||||
|
||||
foreach ($request->allRequestedFields() as $field) {
|
||||
$value = Auth_OpenID::arrayGet($data, $field);
|
||||
if ($value !== null) {
|
||||
$obj->data[$field] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a C{L{SRegResponse}} object from a successful OpenID
|
||||
* library response
|
||||
* (C{L{openid.consumer.consumer.SuccessResponse}}) response
|
||||
* message
|
||||
*
|
||||
* success_response: A SuccessResponse from consumer.complete()
|
||||
*
|
||||
* signed_only: Whether to process only data that was
|
||||
* signed in the id_res message from the server.
|
||||
*
|
||||
* Returns a simple registration response containing the data that
|
||||
* was supplied with the C{id_res} response.
|
||||
*/
|
||||
function fromSuccessResponse(&$success_response, $signed_only=true)
|
||||
{
|
||||
global $Auth_OpenID_sreg_data_fields;
|
||||
|
||||
$obj = new Auth_OpenID_SRegResponse();
|
||||
$obj->ns_uri = $obj->_getSRegNS($success_response->message);
|
||||
|
||||
if ($signed_only) {
|
||||
$args = $success_response->getSignedNS($obj->ns_uri);
|
||||
} else {
|
||||
$args = $success_response->message->getArgs($obj->ns_uri);
|
||||
}
|
||||
|
||||
if ($args === null || Auth_OpenID::isFailure($args)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
|
||||
if (in_array($field_name, array_keys($args))) {
|
||||
$obj->data[$field_name] = $args[$field_name];
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
function getExtensionArgs()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
// Read-only dictionary interface
|
||||
function get($field_name, $default=null)
|
||||
{
|
||||
if (!Auth_OpenID_checkFieldName($field_name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Auth_OpenID::arrayGet($this->data, $field_name, $default);
|
||||
}
|
||||
|
||||
function contents()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
1754
extlib/Auth/OpenID/Server.php
Normal file
1754
extlib/Auth/OpenID/Server.php
Normal file
File diff suppressed because it is too large
Load Diff
37
extlib/Auth/OpenID/ServerRequest.php
Normal file
37
extlib/Auth/OpenID/ServerRequest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* OpenID Server Request
|
||||
*
|
||||
* @see Auth_OpenID_Server
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports
|
||||
*/
|
||||
require_once "Auth/OpenID.php";
|
||||
|
||||
/**
|
||||
* Object that holds the state of a request to the OpenID server
|
||||
*
|
||||
* With accessor functions to get at the internal request data.
|
||||
*
|
||||
* @see Auth_OpenID_Server
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_ServerRequest {
|
||||
function Auth_OpenID_ServerRequest()
|
||||
{
|
||||
$this->mode = null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
462
extlib/Auth/OpenID/TrustRoot.php
Normal file
462
extlib/Auth/OpenID/TrustRoot.php
Normal file
@ -0,0 +1,462 @@
|
||||
<?php
|
||||
/**
|
||||
* Functions for dealing with OpenID trust roots
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
require_once 'Auth/OpenID/Discover.php';
|
||||
|
||||
/**
|
||||
* A regular expression that matches a domain ending in a top-level domains.
|
||||
* Used in checking trust roots for sanity.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
define('Auth_OpenID___TLDs',
|
||||
'/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
|
||||
'|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
|
||||
'|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
|
||||
'|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
|
||||
'|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
|
||||
'|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
|
||||
'|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
|
||||
'|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
|
||||
'|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
|
||||
'|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
|
||||
'|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
|
||||
'|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
|
||||
'|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
|
||||
'|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
|
||||
'|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
|
||||
'|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
|
||||
'|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
|
||||
'|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');
|
||||
|
||||
define('Auth_OpenID___HostSegmentRe',
|
||||
"/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");
|
||||
|
||||
/**
|
||||
* A wrapper for trust-root related functions
|
||||
*/
|
||||
class Auth_OpenID_TrustRoot {
|
||||
/*
|
||||
* Return a discovery URL for this realm.
|
||||
*
|
||||
* Return null if the realm could not be parsed or was not valid.
|
||||
*
|
||||
* @param return_to The relying party return URL of the OpenID
|
||||
* authentication request
|
||||
*
|
||||
* @return The URL upon which relying party discovery should be
|
||||
* run in order to verify the return_to URL
|
||||
*/
|
||||
function buildDiscoveryURL($realm)
|
||||
{
|
||||
$parsed = Auth_OpenID_TrustRoot::_parse($realm);
|
||||
|
||||
if ($parsed === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($parsed['wildcard']) {
|
||||
// Use "www." in place of the star
|
||||
if ($parsed['host'][0] != '.') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$www_domain = 'www' . $parsed['host'];
|
||||
|
||||
return sprintf('%s://%s%s', $parsed['scheme'],
|
||||
$www_domain, $parsed['path']);
|
||||
} else {
|
||||
return $parsed['unparsed'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a URL into its trust_root parts.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $trust_root The url to parse
|
||||
*
|
||||
* @return mixed $parsed Either an associative array of trust root
|
||||
* parts or false if parsing failed.
|
||||
*/
|
||||
function _parse($trust_root)
|
||||
{
|
||||
$trust_root = Auth_OpenID_urinorm($trust_root);
|
||||
if ($trust_root === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parts = @parse_url($trust_root);
|
||||
if ($parts === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$required_parts = array('scheme', 'host');
|
||||
$forbidden_parts = array('user', 'pass', 'fragment');
|
||||
$keys = array_keys($parts);
|
||||
if (array_intersect($keys, $required_parts) != $required_parts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array_intersect($keys, $forbidden_parts) != array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scheme = strtolower($parts['scheme']);
|
||||
$allowed_schemes = array('http', 'https');
|
||||
if (!in_array($scheme, $allowed_schemes)) {
|
||||
return false;
|
||||
}
|
||||
$parts['scheme'] = $scheme;
|
||||
|
||||
$host = strtolower($parts['host']);
|
||||
$hostparts = explode('*', $host);
|
||||
switch (count($hostparts)) {
|
||||
case 1:
|
||||
$parts['wildcard'] = false;
|
||||
break;
|
||||
case 2:
|
||||
if ($hostparts[0] ||
|
||||
($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
|
||||
return false;
|
||||
}
|
||||
$host = $hostparts[1];
|
||||
$parts['wildcard'] = true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (strpos($host, ':') !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parts['host'] = $host;
|
||||
|
||||
if (isset($parts['path'])) {
|
||||
$path = strtolower($parts['path']);
|
||||
if (substr($path, 0, 1) != '/') {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
$parts['path'] = $path;
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = false;
|
||||
}
|
||||
|
||||
|
||||
$parts['unparsed'] = $trust_root;
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this trust root sane?
|
||||
*
|
||||
* A trust root is sane if it is syntactically valid and it has a
|
||||
* reasonable domain name. Specifically, the domain name must be
|
||||
* more than one level below a standard TLD or more than two
|
||||
* levels below a two-letter tld.
|
||||
*
|
||||
* For example, '*.com' is not a sane trust root, but '*.foo.com'
|
||||
* is. '*.co.uk' is not sane, but '*.bbc.co.uk' is.
|
||||
*
|
||||
* This check is not always correct, but it attempts to err on the
|
||||
* side of marking sane trust roots insane instead of marking
|
||||
* insane trust roots sane. For example, 'kink.fm' is marked as
|
||||
* insane even though it "should" (for some meaning of should) be
|
||||
* marked sane.
|
||||
*
|
||||
* This function should be used when creating OpenID servers to
|
||||
* alert the users of the server when a consumer attempts to get
|
||||
* the user to accept a suspicious trust root.
|
||||
*
|
||||
* @static
|
||||
* @param string $trust_root The trust root to check
|
||||
* @return bool $sanity Whether the trust root looks OK
|
||||
*/
|
||||
function isSane($trust_root)
|
||||
{
|
||||
$parts = Auth_OpenID_TrustRoot::_parse($trust_root);
|
||||
if ($parts === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Localhost is a special case
|
||||
if ($parts['host'] == 'localhost') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$host_parts = explode('.', $parts['host']);
|
||||
if ($parts['wildcard']) {
|
||||
// Remove the empty string from the beginning of the array
|
||||
array_shift($host_parts);
|
||||
}
|
||||
|
||||
if ($host_parts && !$host_parts[count($host_parts) - 1]) {
|
||||
array_pop($host_parts);
|
||||
}
|
||||
|
||||
if (!$host_parts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow adjacent dots
|
||||
if (in_array('', $host_parts, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the top-level domain of the host. If it is not a valid TLD,
|
||||
// it's not sane.
|
||||
preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
|
||||
if (!$matches) {
|
||||
return false;
|
||||
}
|
||||
$tld = $matches[1];
|
||||
|
||||
if (count($host_parts) == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($parts['wildcard']) {
|
||||
// It's a 2-letter tld with a short second to last segment
|
||||
// so there needs to be more than two segments specified
|
||||
// (e.g. *.co.uk is insane)
|
||||
$second_level = $host_parts[count($host_parts) - 2];
|
||||
if (strlen($tld) == 2 && strlen($second_level) <= 3) {
|
||||
return count($host_parts) > 2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this URL match the given trust root?
|
||||
*
|
||||
* Return whether the URL falls under the given trust root. This
|
||||
* does not check whether the trust root is sane. If the URL or
|
||||
* trust root do not parse, this function will return false.
|
||||
*
|
||||
* @param string $trust_root The trust root to match against
|
||||
*
|
||||
* @param string $url The URL to check
|
||||
*
|
||||
* @return bool $matches Whether the URL matches against the
|
||||
* trust root
|
||||
*/
|
||||
function match($trust_root, $url)
|
||||
{
|
||||
$trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
|
||||
$url_parsed = Auth_OpenID_TrustRoot::_parse($url);
|
||||
if (!$trust_root_parsed || !$url_parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check hosts matching
|
||||
if ($url_parsed['wildcard']) {
|
||||
return false;
|
||||
}
|
||||
if ($trust_root_parsed['wildcard']) {
|
||||
$host_tail = $trust_root_parsed['host'];
|
||||
$host = $url_parsed['host'];
|
||||
if ($host_tail &&
|
||||
substr($host, -(strlen($host_tail))) != $host_tail &&
|
||||
substr($host_tail, 1) != $host) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ($trust_root_parsed['host'] != $url_parsed['host']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check path and query matching
|
||||
$base_path = $trust_root_parsed['path'];
|
||||
$path = $url_parsed['path'];
|
||||
if (!isset($trust_root_parsed['query'])) {
|
||||
if ($base_path != $path) {
|
||||
if (substr($path, 0, strlen($base_path)) != $base_path) {
|
||||
return false;
|
||||
}
|
||||
if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
|
||||
substr($path, strlen($base_path), 1) != '/') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$base_query = $trust_root_parsed['query'];
|
||||
$query = @$url_parsed['query'];
|
||||
$qplus = substr($query, 0, strlen($base_query) + 1);
|
||||
$bqplus = $base_query . '&';
|
||||
if ($base_path != $path ||
|
||||
($base_query != $query && $qplus != $bqplus)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The port and scheme need to match exactly
|
||||
return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
|
||||
$url_parsed['port'] === $trust_root_parsed['port']);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the endpoint is a relying party OpenID return_to endpoint,
|
||||
* return the endpoint URL. Otherwise, return None.
|
||||
*
|
||||
* This function is intended to be used as a filter for the Yadis
|
||||
* filtering interface.
|
||||
*
|
||||
* @see: C{L{openid.yadis.services}}
|
||||
* @see: C{L{openid.yadis.filters}}
|
||||
*
|
||||
* @param endpoint: An XRDS BasicServiceEndpoint, as returned by
|
||||
* performing Yadis dicovery.
|
||||
*
|
||||
* @returns: The endpoint URL or None if the endpoint is not a
|
||||
* relying party endpoint.
|
||||
*/
|
||||
function filter_extractReturnURL(&$endpoint)
|
||||
{
|
||||
if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
|
||||
return $endpoint;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function &Auth_OpenID_extractReturnURL(&$endpoint_list)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($endpoint_list as $endpoint) {
|
||||
if (filter_extractReturnURL($endpoint)) {
|
||||
$result[] = $endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is the return_to URL under one of the supplied allowed return_to
|
||||
* URLs?
|
||||
*/
|
||||
function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
|
||||
{
|
||||
foreach ($allowed_return_to_urls as $allowed_return_to) {
|
||||
// A return_to pattern works the same as a realm, except that
|
||||
// it's not allowed to use a wildcard. We'll model this by
|
||||
// parsing it as a realm, and not trying to match it if it has
|
||||
// a wildcard.
|
||||
|
||||
$return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
|
||||
if (// Parses as a trust root
|
||||
($return_realm !== false) &&
|
||||
// Does not have a wildcard
|
||||
(!$return_realm['wildcard']) &&
|
||||
// Matches the return_to that we passed in with it
|
||||
(Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No URL in the list matched
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a relying party discovery URL return a list of return_to
|
||||
* URLs.
|
||||
*/
|
||||
function Auth_OpenID_getAllowedReturnURLs($relying_party_url, &$fetcher,
|
||||
$discover_function=null)
|
||||
{
|
||||
if ($discover_function === null) {
|
||||
$discover_function = array('Auth_Yadis_Yadis', 'discover');
|
||||
}
|
||||
|
||||
$xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'fromXRDS');
|
||||
|
||||
list($rp_url_after_redirects, $endpoints) =
|
||||
Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
|
||||
$discover_function, $fetcher);
|
||||
|
||||
if ($rp_url_after_redirects != $relying_party_url) {
|
||||
// Verification caused a redirect
|
||||
return false;
|
||||
}
|
||||
|
||||
call_user_func_array($discover_function,
|
||||
array($relying_party_url, $fetcher));
|
||||
|
||||
$return_to_urls = array();
|
||||
$matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);
|
||||
|
||||
foreach ($matching_endpoints as $e) {
|
||||
$return_to_urls[] = $e->server_url;
|
||||
}
|
||||
|
||||
return $return_to_urls;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that a return_to URL is valid for the given realm.
|
||||
*
|
||||
* This function builds a discovery URL, performs Yadis discovery on
|
||||
* it, makes sure that the URL does not redirect, parses out the
|
||||
* return_to URLs, and finally checks to see if the current return_to
|
||||
* URL matches the return_to.
|
||||
*
|
||||
* @return true if the return_to URL is valid for the realm
|
||||
*/
|
||||
function Auth_OpenID_verifyReturnTo($realm_str, $return_to, &$fetcher,
|
||||
$_vrfy='Auth_OpenID_getAllowedReturnURLs')
|
||||
{
|
||||
$disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);
|
||||
|
||||
if ($disco_url === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowable_urls = call_user_func_array($_vrfy,
|
||||
array($disco_url, &$fetcher));
|
||||
|
||||
// The realm_str could not be parsed.
|
||||
if ($allowable_urls === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
249
extlib/Auth/OpenID/URINorm.php
Normal file
249
extlib/Auth/OpenID/URINorm.php
Normal file
@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* URI normalization routines.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
require_once 'Auth/Yadis/Misc.php';
|
||||
|
||||
// from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt)
|
||||
function Auth_OpenID_getURIPattern()
|
||||
{
|
||||
return '&^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?&';
|
||||
}
|
||||
|
||||
function Auth_OpenID_getAuthorityPattern()
|
||||
{
|
||||
return '/^([^@]*@)?([^:]*)(:.*)?/';
|
||||
}
|
||||
|
||||
function Auth_OpenID_getEncodedPattern()
|
||||
{
|
||||
return '/%([0-9A-Fa-f]{2})/';
|
||||
}
|
||||
|
||||
# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
||||
#
|
||||
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
# / "*" / "+" / "," / ";" / "="
|
||||
#
|
||||
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
function Auth_OpenID_getURLIllegalCharRE()
|
||||
{
|
||||
return "/([^-A-Za-z0-9:\/\?#\[\]@\!\$&'\(\)\*\+,;=\._~\%])/";
|
||||
}
|
||||
|
||||
function Auth_OpenID_getUnreserved()
|
||||
{
|
||||
$_unreserved = array();
|
||||
for ($i = 0; $i < 256; $i++) {
|
||||
$_unreserved[$i] = false;
|
||||
}
|
||||
|
||||
for ($i = ord('A'); $i <= ord('Z'); $i++) {
|
||||
$_unreserved[$i] = true;
|
||||
}
|
||||
|
||||
for ($i = ord('0'); $i <= ord('9'); $i++) {
|
||||
$_unreserved[$i] = true;
|
||||
}
|
||||
|
||||
for ($i = ord('a'); $i <= ord('z'); $i++) {
|
||||
$_unreserved[$i] = true;
|
||||
}
|
||||
|
||||
$_unreserved[ord('-')] = true;
|
||||
$_unreserved[ord('.')] = true;
|
||||
$_unreserved[ord('_')] = true;
|
||||
$_unreserved[ord('~')] = true;
|
||||
|
||||
return $_unreserved;
|
||||
}
|
||||
|
||||
function Auth_OpenID_getEscapeRE()
|
||||
{
|
||||
$parts = array();
|
||||
foreach (array_merge(Auth_Yadis_getUCSChars(),
|
||||
Auth_Yadis_getIPrivateChars()) as $pair) {
|
||||
list($m, $n) = $pair;
|
||||
$parts[] = sprintf("%s-%s", chr($m), chr($n));
|
||||
}
|
||||
|
||||
return sprintf('[%s]', implode('', $parts));
|
||||
}
|
||||
|
||||
function Auth_OpenID_pct_encoded_replace_unreserved($mo)
|
||||
{
|
||||
$_unreserved = Auth_OpenID_getUnreserved();
|
||||
|
||||
$i = intval($mo[1], 16);
|
||||
if ($_unreserved[$i]) {
|
||||
return chr($i);
|
||||
} else {
|
||||
return strtoupper($mo[0]);
|
||||
}
|
||||
|
||||
return $mo[0];
|
||||
}
|
||||
|
||||
function Auth_OpenID_pct_encoded_replace($mo)
|
||||
{
|
||||
return chr(intval($mo[1], 16));
|
||||
}
|
||||
|
||||
function Auth_OpenID_remove_dot_segments($path)
|
||||
{
|
||||
$result_segments = array();
|
||||
|
||||
while ($path) {
|
||||
if (Auth_Yadis_startswith($path, '../')) {
|
||||
$path = substr($path, 3);
|
||||
} else if (Auth_Yadis_startswith($path, './')) {
|
||||
$path = substr($path, 2);
|
||||
} else if (Auth_Yadis_startswith($path, '/./')) {
|
||||
$path = substr($path, 2);
|
||||
} else if ($path == '/.') {
|
||||
$path = '/';
|
||||
} else if (Auth_Yadis_startswith($path, '/../')) {
|
||||
$path = substr($path, 3);
|
||||
if ($result_segments) {
|
||||
array_pop($result_segments);
|
||||
}
|
||||
} else if ($path == '/..') {
|
||||
$path = '/';
|
||||
if ($result_segments) {
|
||||
array_pop($result_segments);
|
||||
}
|
||||
} else if (($path == '..') ||
|
||||
($path == '.')) {
|
||||
$path = '';
|
||||
} else {
|
||||
$i = 0;
|
||||
if ($path[0] == '/') {
|
||||
$i = 1;
|
||||
}
|
||||
$i = strpos($path, '/', $i);
|
||||
if ($i === false) {
|
||||
$i = strlen($path);
|
||||
}
|
||||
$result_segments[] = substr($path, 0, $i);
|
||||
$path = substr($path, $i);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $result_segments);
|
||||
}
|
||||
|
||||
function Auth_OpenID_urinorm($uri)
|
||||
{
|
||||
$uri_matches = array();
|
||||
preg_match(Auth_OpenID_getURIPattern(), $uri, $uri_matches);
|
||||
|
||||
if (count($uri_matches) < 9) {
|
||||
for ($i = count($uri_matches); $i <= 9; $i++) {
|
||||
$uri_matches[] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$illegal_matches = array();
|
||||
preg_match(Auth_OpenID_getURLIllegalCharRE(),
|
||||
$uri, $illegal_matches);
|
||||
if ($illegal_matches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scheme = $uri_matches[2];
|
||||
if ($scheme) {
|
||||
$scheme = strtolower($scheme);
|
||||
}
|
||||
|
||||
$scheme = $uri_matches[2];
|
||||
if ($scheme === '') {
|
||||
// No scheme specified
|
||||
return null;
|
||||
}
|
||||
|
||||
$scheme = strtolower($scheme);
|
||||
if (!in_array($scheme, array('http', 'https'))) {
|
||||
// Not an absolute HTTP or HTTPS URI
|
||||
return null;
|
||||
}
|
||||
|
||||
$authority = $uri_matches[4];
|
||||
if ($authority === '') {
|
||||
// Not an absolute URI
|
||||
return null;
|
||||
}
|
||||
|
||||
$authority_matches = array();
|
||||
preg_match(Auth_OpenID_getAuthorityPattern(),
|
||||
$authority, $authority_matches);
|
||||
if (count($authority_matches) === 0) {
|
||||
// URI does not have a valid authority
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count($authority_matches) < 4) {
|
||||
for ($i = count($authority_matches); $i <= 4; $i++) {
|
||||
$authority_matches[] = '';
|
||||
}
|
||||
}
|
||||
|
||||
list($_whole, $userinfo, $host, $port) = $authority_matches;
|
||||
|
||||
if ($userinfo === null) {
|
||||
$userinfo = '';
|
||||
}
|
||||
|
||||
if (strpos($host, '%') !== -1) {
|
||||
$host = strtolower($host);
|
||||
$host = preg_replace_callback(
|
||||
Auth_OpenID_getEncodedPattern(),
|
||||
'Auth_OpenID_pct_encoded_replace', $host);
|
||||
// NO IDNA.
|
||||
// $host = unicode($host, 'utf-8').encode('idna');
|
||||
} else {
|
||||
$host = strtolower($host);
|
||||
}
|
||||
|
||||
if ($port) {
|
||||
if (($port == ':') ||
|
||||
($scheme == 'http' && $port == ':80') ||
|
||||
($scheme == 'https' && $port == ':443')) {
|
||||
$port = '';
|
||||
}
|
||||
} else {
|
||||
$port = '';
|
||||
}
|
||||
|
||||
$authority = $userinfo . $host . $port;
|
||||
|
||||
$path = $uri_matches[5];
|
||||
$path = preg_replace_callback(
|
||||
Auth_OpenID_getEncodedPattern(),
|
||||
'Auth_OpenID_pct_encoded_replace_unreserved', $path);
|
||||
|
||||
$path = Auth_OpenID_remove_dot_segments($path);
|
||||
if (!$path) {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
$query = $uri_matches[6];
|
||||
if ($query === null) {
|
||||
$query = '';
|
||||
}
|
||||
|
||||
$fragment = $uri_matches[8];
|
||||
if ($fragment === null) {
|
||||
$fragment = '';
|
||||
}
|
||||
|
||||
return $scheme . '://' . $authority . $path . $query . $fragment;
|
||||
}
|
||||
|
||||
?>
|
147
extlib/Auth/Yadis/HTTPFetcher.php
Normal file
147
extlib/Auth/Yadis/HTTPFetcher.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module contains the HTTP fetcher interface
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require logging functionality
|
||||
*/
|
||||
require_once "Auth/OpenID.php";
|
||||
|
||||
define('Auth_OpenID_FETCHER_MAX_RESPONSE_KB', 1024);
|
||||
define('Auth_OpenID_USER_AGENT',
|
||||
'php-openid/'.Auth_OpenID_VERSION.' (php/'.phpversion().')');
|
||||
|
||||
class Auth_Yadis_HTTPResponse {
|
||||
function Auth_Yadis_HTTPResponse($final_url = null, $status = null,
|
||||
$headers = null, $body = null)
|
||||
{
|
||||
$this->final_url = $final_url;
|
||||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
$this->body = $body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is the interface for HTTP fetchers the Yadis library
|
||||
* uses. This interface is only important if you need to write a new
|
||||
* fetcher for some reason.
|
||||
*
|
||||
* @access private
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_HTTPFetcher {
|
||||
|
||||
var $timeout = 20; // timeout in seconds.
|
||||
|
||||
/**
|
||||
* Return whether a URL can be fetched. Returns false if the URL
|
||||
* scheme is not allowed or is not supported by this fetcher
|
||||
* implementation; returns true otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function canFetchURL($url)
|
||||
{
|
||||
if ($this->isHTTPS($url) && !$this->supportsSSL()) {
|
||||
Auth_OpenID::log("HTTPS URL unsupported fetching %s",
|
||||
$url);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->allowedURL($url)) {
|
||||
Auth_OpenID::log("URL fetching not allowed for '%s'",
|
||||
$url);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether a URL should be allowed. Override this method to
|
||||
* conform to your local policy.
|
||||
*
|
||||
* By default, will attempt to fetch any http or https URL.
|
||||
*/
|
||||
function allowedURL($url)
|
||||
{
|
||||
return $this->URLHasAllowedScheme($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this fetcher implementation (and runtime) support fetching
|
||||
* HTTPS URLs? May inspect the runtime environment.
|
||||
*
|
||||
* @return bool $support True if this fetcher supports HTTPS
|
||||
* fetching; false if not.
|
||||
*/
|
||||
function supportsSSL()
|
||||
{
|
||||
trigger_error("not implemented", E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an https URL?
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function isHTTPS($url)
|
||||
{
|
||||
return (bool)preg_match('/^https:\/\//i', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an http or https URL?
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function URLHasAllowedScheme($url)
|
||||
{
|
||||
return (bool)preg_match('/^https?:\/\//i', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _findRedirect($headers)
|
||||
{
|
||||
foreach ($headers as $line) {
|
||||
if (strpos(strtolower($line), "location: ") === 0) {
|
||||
$parts = explode(" ", $line, 2);
|
||||
return $parts[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the specified URL using optional extra headers and
|
||||
* returns the server's response.
|
||||
*
|
||||
* @param string $url The URL to be fetched.
|
||||
* @param array $extra_headers An array of header strings
|
||||
* (e.g. "Accept: text/html").
|
||||
* @return mixed $result An array of ($code, $url, $headers,
|
||||
* $body) if the URL could be fetched; null if the URL does not
|
||||
* pass the URLHasAllowedScheme check or if the server's response
|
||||
* is malformed.
|
||||
*/
|
||||
function get($url, $headers)
|
||||
{
|
||||
trigger_error("not implemented", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
529
extlib/Auth/Yadis/Manager.php
Normal file
529
extlib/Auth/Yadis/Manager.php
Normal file
@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Yadis service manager to be used during yadis-driven authentication
|
||||
* attempts.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* The base session class used by the Auth_Yadis_Manager. This
|
||||
* class wraps the default PHP session machinery and should be
|
||||
* subclassed if your application doesn't use PHP sessioning.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_PHPSession {
|
||||
/**
|
||||
* Set a session key/value pair.
|
||||
*
|
||||
* @param string $name The name of the session key to add.
|
||||
* @param string $value The value to add to the session.
|
||||
*/
|
||||
function set($name, $value)
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key's value from the session.
|
||||
*
|
||||
* @param string $name The name of the key to retrieve.
|
||||
* @param string $default The optional value to return if the key
|
||||
* is not found in the session.
|
||||
* @return string $result The key's value in the session or
|
||||
* $default if it isn't found.
|
||||
*/
|
||||
function get($name, $default=null)
|
||||
{
|
||||
if (array_key_exists($name, $_SESSION)) {
|
||||
return $_SESSION[$name];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key/value pair from the session.
|
||||
*
|
||||
* @param string $name The name of the key to remove.
|
||||
*/
|
||||
function del($name)
|
||||
{
|
||||
unset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of the session in array form.
|
||||
*/
|
||||
function contents()
|
||||
{
|
||||
return $_SESSION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A session helper class designed to translate between arrays and
|
||||
* objects. Note that the class used must have a constructor that
|
||||
* takes no parameters. This is not a general solution, but it works
|
||||
* for dumb objects that just need to have attributes set. The idea
|
||||
* is that you'll subclass this and override $this->check($data) ->
|
||||
* bool to implement your own session data validation.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_SessionLoader {
|
||||
/**
|
||||
* Override this.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function check($data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a session data value (an array), this creates an object
|
||||
* (returned by $this->newObject()) whose attributes and values
|
||||
* are those in $data. Returns null if $data lacks keys found in
|
||||
* $this->requiredKeys(). Returns null if $this->check($data)
|
||||
* evaluates to false. Returns null if $this->newObject()
|
||||
* evaluates to false.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function fromSession($data)
|
||||
{
|
||||
if (!$data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$required = $this->requiredKeys();
|
||||
|
||||
foreach ($required as $k) {
|
||||
if (!array_key_exists($k, $data)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->check($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = array_merge($data, $this->prepareForLoad($data));
|
||||
$obj = $this->newObject($data);
|
||||
|
||||
if (!$obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($required as $k) {
|
||||
$obj->$k = $data[$k];
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the data array by making any necessary changes.
|
||||
* Returns an array whose keys and values will be used to update
|
||||
* the original data array before calling $this->newObject($data).
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function prepareForLoad($data)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of this loader's class, using the
|
||||
* session data to construct it if necessary. The object need
|
||||
* only be created; $this->fromSession() will take care of setting
|
||||
* the object's attributes.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function newObject($data)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of keys and values built from the attributes
|
||||
* of $obj. If $this->prepareForSave($obj) returns an array, its keys
|
||||
* and values are used to update the $data array of attributes
|
||||
* from $obj.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function toSession($obj)
|
||||
{
|
||||
$data = array();
|
||||
foreach ($obj as $k => $v) {
|
||||
$data[$k] = $v;
|
||||
}
|
||||
|
||||
$extra = $this->prepareForSave($obj);
|
||||
|
||||
if ($extra && is_array($extra)) {
|
||||
foreach ($extra as $k => $v) {
|
||||
$data[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function prepareForSave($obj)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
|
||||
function newObject($data)
|
||||
{
|
||||
return new Auth_OpenID_ServiceEndpoint();
|
||||
}
|
||||
|
||||
function requiredKeys()
|
||||
{
|
||||
$obj = new Auth_OpenID_ServiceEndpoint();
|
||||
$data = array();
|
||||
foreach ($obj as $k => $v) {
|
||||
$data[] = $k;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
function check($data)
|
||||
{
|
||||
return is_array($data['type_uris']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete loader implementation for Auth_Yadis_Managers.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
|
||||
function requiredKeys()
|
||||
{
|
||||
return array('starting_url',
|
||||
'yadis_url',
|
||||
'services',
|
||||
'session_key',
|
||||
'_current',
|
||||
'stale');
|
||||
}
|
||||
|
||||
function newObject($data)
|
||||
{
|
||||
return new Auth_Yadis_Manager($data['starting_url'],
|
||||
$data['yadis_url'],
|
||||
$data['services'],
|
||||
$data['session_key']);
|
||||
}
|
||||
|
||||
function check($data)
|
||||
{
|
||||
return is_array($data['services']);
|
||||
}
|
||||
|
||||
function prepareForLoad($data)
|
||||
{
|
||||
$loader = new Auth_OpenID_ServiceEndpointLoader();
|
||||
$services = array();
|
||||
foreach ($data['services'] as $s) {
|
||||
$services[] = $loader->fromSession($s);
|
||||
}
|
||||
return array('services' => $services);
|
||||
}
|
||||
|
||||
function prepareForSave($obj)
|
||||
{
|
||||
$loader = new Auth_OpenID_ServiceEndpointLoader();
|
||||
$services = array();
|
||||
foreach ($obj->services as $s) {
|
||||
$services[] = $loader->toSession($s);
|
||||
}
|
||||
return array('services' => $services);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Yadis service manager which stores state in a session and
|
||||
* iterates over <Service> elements in a Yadis XRDS document and lets
|
||||
* a caller attempt to use each one. This is used by the Yadis
|
||||
* library internally.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_Manager {
|
||||
|
||||
/**
|
||||
* Intialize a new yadis service manager.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function Auth_Yadis_Manager($starting_url, $yadis_url,
|
||||
$services, $session_key)
|
||||
{
|
||||
// The URL that was used to initiate the Yadis protocol
|
||||
$this->starting_url = $starting_url;
|
||||
|
||||
// The URL after following redirects (the identifier)
|
||||
$this->yadis_url = $yadis_url;
|
||||
|
||||
// List of service elements
|
||||
$this->services = $services;
|
||||
|
||||
$this->session_key = $session_key;
|
||||
|
||||
// Reference to the current service object
|
||||
$this->_current = null;
|
||||
|
||||
// Stale flag for cleanup if PHP lib has trouble.
|
||||
$this->stale = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function length()
|
||||
{
|
||||
// How many untried services remain?
|
||||
return count($this->services);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next service
|
||||
*
|
||||
* $this->current() will continue to return that service until the
|
||||
* next call to this method.
|
||||
*/
|
||||
function nextService()
|
||||
{
|
||||
|
||||
if ($this->services) {
|
||||
$this->_current = array_shift($this->services);
|
||||
} else {
|
||||
$this->_current = null;
|
||||
}
|
||||
|
||||
return $this->_current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function current()
|
||||
{
|
||||
// Return the current service.
|
||||
// Returns None if there are no services left.
|
||||
return $this->_current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function forURL($url)
|
||||
{
|
||||
return in_array($url, array($this->starting_url, $this->yadis_url));
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function started()
|
||||
{
|
||||
// Has the first service been returned?
|
||||
return $this->_current !== null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State management for discovery.
|
||||
*
|
||||
* High-level usage pattern is to call .getNextService(discover) in
|
||||
* order to find the next available service for this user for this
|
||||
* session. Once a request completes, call .cleanup() to clean up the
|
||||
* session state.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_Discovery {
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $DEFAULT_SUFFIX = 'auth';
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $PREFIX = '_yadis_services_';
|
||||
|
||||
/**
|
||||
* Initialize a discovery object.
|
||||
*
|
||||
* @param Auth_Yadis_PHPSession $session An object which
|
||||
* implements the Auth_Yadis_PHPSession API.
|
||||
* @param string $url The URL on which to attempt discovery.
|
||||
* @param string $session_key_suffix The optional session key
|
||||
* suffix override.
|
||||
*/
|
||||
function Auth_Yadis_Discovery(&$session, $url,
|
||||
$session_key_suffix = null)
|
||||
{
|
||||
/// Initialize a discovery object
|
||||
$this->session =& $session;
|
||||
$this->url = $url;
|
||||
if ($session_key_suffix === null) {
|
||||
$session_key_suffix = $this->DEFAULT_SUFFIX;
|
||||
}
|
||||
|
||||
$this->session_key_suffix = $session_key_suffix;
|
||||
$this->session_key = $this->PREFIX . $this->session_key_suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next authentication service for the pair of
|
||||
* user_input and session. This function handles fallback.
|
||||
*/
|
||||
function getNextService($discover_cb, &$fetcher)
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
if (!$manager || (!$manager->services)) {
|
||||
$this->destroyManager();
|
||||
|
||||
list($yadis_url, $services) = call_user_func($discover_cb,
|
||||
$this->url,
|
||||
$fetcher);
|
||||
|
||||
$manager = $this->createManager($services, $yadis_url);
|
||||
}
|
||||
|
||||
if ($manager) {
|
||||
$loader = new Auth_Yadis_ManagerLoader();
|
||||
$service = $manager->nextService();
|
||||
$this->session->set($this->session_key,
|
||||
serialize($loader->toSession($manager)));
|
||||
} else {
|
||||
$service = null;
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up Yadis-related services in the session and return the
|
||||
* most-recently-attempted service from the manager, if one
|
||||
* exists.
|
||||
*
|
||||
* @param $force True if the manager should be deleted regardless
|
||||
* of whether it's a manager for $this->url.
|
||||
*/
|
||||
function cleanup($force=false)
|
||||
{
|
||||
$manager = $this->getManager($force);
|
||||
if ($manager) {
|
||||
$service = $manager->current();
|
||||
$this->destroyManager($force);
|
||||
} else {
|
||||
$service = null;
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function getSessionKey()
|
||||
{
|
||||
// Get the session key for this starting URL and suffix
|
||||
return $this->PREFIX . $this->session_key_suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*
|
||||
* @param $force True if the manager should be returned regardless
|
||||
* of whether it's a manager for $this->url.
|
||||
*/
|
||||
function &getManager($force=false)
|
||||
{
|
||||
// Extract the YadisServiceManager for this object's URL and
|
||||
// suffix from the session.
|
||||
|
||||
$manager_str = $this->session->get($this->getSessionKey());
|
||||
$manager = null;
|
||||
|
||||
if ($manager_str !== null) {
|
||||
$loader = new Auth_Yadis_ManagerLoader();
|
||||
$manager = $loader->fromSession(unserialize($manager_str));
|
||||
}
|
||||
|
||||
if ($manager && ($manager->forURL($this->url) || $force)) {
|
||||
return $manager;
|
||||
} else {
|
||||
$unused = null;
|
||||
return $unused;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function &createManager($services, $yadis_url = null)
|
||||
{
|
||||
$key = $this->getSessionKey();
|
||||
if ($this->getManager()) {
|
||||
return $this->getManager();
|
||||
}
|
||||
|
||||
if ($services) {
|
||||
$loader = new Auth_Yadis_ManagerLoader();
|
||||
$manager = new Auth_Yadis_Manager($this->url, $yadis_url,
|
||||
$services, $key);
|
||||
$this->session->set($this->session_key,
|
||||
serialize($loader->toSession($manager)));
|
||||
return $manager;
|
||||
} else {
|
||||
// Oh, PHP.
|
||||
$unused = null;
|
||||
return $unused;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*
|
||||
* @param $force True if the manager should be deleted regardless
|
||||
* of whether it's a manager for $this->url.
|
||||
*/
|
||||
function destroyManager($force=false)
|
||||
{
|
||||
if ($this->getManager($force) !== null) {
|
||||
$key = $this->getSessionKey();
|
||||
$this->session->del($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
59
extlib/Auth/Yadis/Misc.php
Normal file
59
extlib/Auth/Yadis/Misc.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Miscellaneous utility values and functions for OpenID and Yadis.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
function Auth_Yadis_getUCSChars()
|
||||
{
|
||||
return array(
|
||||
array(0xA0, 0xD7FF),
|
||||
array(0xF900, 0xFDCF),
|
||||
array(0xFDF0, 0xFFEF),
|
||||
array(0x10000, 0x1FFFD),
|
||||
array(0x20000, 0x2FFFD),
|
||||
array(0x30000, 0x3FFFD),
|
||||
array(0x40000, 0x4FFFD),
|
||||
array(0x50000, 0x5FFFD),
|
||||
array(0x60000, 0x6FFFD),
|
||||
array(0x70000, 0x7FFFD),
|
||||
array(0x80000, 0x8FFFD),
|
||||
array(0x90000, 0x9FFFD),
|
||||
array(0xA0000, 0xAFFFD),
|
||||
array(0xB0000, 0xBFFFD),
|
||||
array(0xC0000, 0xCFFFD),
|
||||
array(0xD0000, 0xDFFFD),
|
||||
array(0xE1000, 0xEFFFD)
|
||||
);
|
||||
}
|
||||
|
||||
function Auth_Yadis_getIPrivateChars()
|
||||
{
|
||||
return array(
|
||||
array(0xE000, 0xF8FF),
|
||||
array(0xF0000, 0xFFFFD),
|
||||
array(0x100000, 0x10FFFD)
|
||||
);
|
||||
}
|
||||
|
||||
function Auth_Yadis_pct_escape_unicode($char_match)
|
||||
{
|
||||
$c = $char_match[0];
|
||||
$result = "";
|
||||
for ($i = 0; $i < strlen($c); $i++) {
|
||||
$result .= "%".sprintf("%X", ord($c[$i]));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function Auth_Yadis_startswith($s, $stuff)
|
||||
{
|
||||
return strpos($s, $stuff) === 0;
|
||||
}
|
||||
|
||||
?>
|
228
extlib/Auth/Yadis/ParanoidHTTPFetcher.php
Normal file
228
extlib/Auth/Yadis/ParanoidHTTPFetcher.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module contains the CURL-based HTTP fetcher implementation.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface import
|
||||
*/
|
||||
require_once "Auth/Yadis/HTTPFetcher.php";
|
||||
|
||||
require_once "Auth/OpenID.php";
|
||||
|
||||
/**
|
||||
* A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
|
||||
* for fetching.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
|
||||
function Auth_Yadis_ParanoidHTTPFetcher()
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
function reset()
|
||||
{
|
||||
$this->headers = array();
|
||||
$this->data = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _writeHeader($ch, $header)
|
||||
{
|
||||
array_push($this->headers, rtrim($header));
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _writeData($ch, $data)
|
||||
{
|
||||
if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
|
||||
return 0;
|
||||
} else {
|
||||
$this->data .= $data;
|
||||
return strlen($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this fetcher support SSL URLs?
|
||||
*/
|
||||
function supportsSSL()
|
||||
{
|
||||
$v = curl_version();
|
||||
if(is_array($v)) {
|
||||
return in_array('https', $v['protocols']);
|
||||
} elseif (is_string($v)) {
|
||||
return preg_match('/OpenSSL/i', $v);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function get($url, $extra_headers = null)
|
||||
{
|
||||
if (!$this->canFetchURL($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stop = time() + $this->timeout;
|
||||
$off = $this->timeout;
|
||||
|
||||
$redir = true;
|
||||
|
||||
while ($redir && ($off > 0)) {
|
||||
$this->reset();
|
||||
|
||||
$c = curl_init();
|
||||
|
||||
if ($c === false) {
|
||||
Auth_OpenID::log(
|
||||
"curl_init returned false; could not " .
|
||||
"initialize for URL '%s'", $url);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (defined('CURLOPT_NOSIGNAL')) {
|
||||
curl_setopt($c, CURLOPT_NOSIGNAL, true);
|
||||
}
|
||||
|
||||
if (!$this->allowedURL($url)) {
|
||||
Auth_OpenID::log("Fetching URL not allowed: %s",
|
||||
$url);
|
||||
return null;
|
||||
}
|
||||
|
||||
curl_setopt($c, CURLOPT_WRITEFUNCTION,
|
||||
array(&$this, "_writeData"));
|
||||
curl_setopt($c, CURLOPT_HEADERFUNCTION,
|
||||
array(&$this, "_writeHeader"));
|
||||
|
||||
if ($extra_headers) {
|
||||
curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
|
||||
}
|
||||
|
||||
$cv = curl_version();
|
||||
if(is_array($cv)) {
|
||||
$curl_user_agent = 'curl/'.$cv['version'];
|
||||
} else {
|
||||
$curl_user_agent = $cv;
|
||||
}
|
||||
curl_setopt($c, CURLOPT_USERAGENT,
|
||||
Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
|
||||
curl_setopt($c, CURLOPT_TIMEOUT, $off);
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
curl_setopt($c, CURLOPT_RANGE,
|
||||
"0-".(1024 * Auth_OpenID_FETCHER_MAX_RESPONSE_KB));
|
||||
|
||||
curl_exec($c);
|
||||
|
||||
$code = curl_getinfo($c, CURLINFO_HTTP_CODE);
|
||||
$body = $this->data;
|
||||
$headers = $this->headers;
|
||||
|
||||
if (!$code) {
|
||||
Auth_OpenID::log("Got no response code when fetching %s", $url);
|
||||
Auth_OpenID::log("CURL error (%s): %s",
|
||||
curl_errno($c), curl_error($c));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (in_array($code, array(301, 302, 303, 307))) {
|
||||
$url = $this->_findRedirect($headers);
|
||||
$redir = true;
|
||||
} else {
|
||||
$redir = false;
|
||||
curl_close($c);
|
||||
|
||||
$new_headers = array();
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (strpos($header, ': ')) {
|
||||
list($name, $value) = explode(': ', $header, 2);
|
||||
$new_headers[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
Auth_OpenID::log(
|
||||
"Successfully fetched '%s': GET response code %s",
|
||||
$url, $code);
|
||||
|
||||
return new Auth_Yadis_HTTPResponse($url, $code,
|
||||
$new_headers, $body);
|
||||
}
|
||||
|
||||
$off = $stop - time();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function post($url, $body, $extra_headers = null)
|
||||
{
|
||||
if (!$this->canFetchURL($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
|
||||
$c = curl_init();
|
||||
|
||||
if (defined('CURLOPT_NOSIGNAL')) {
|
||||
curl_setopt($c, CURLOPT_NOSIGNAL, true);
|
||||
}
|
||||
|
||||
curl_setopt($c, CURLOPT_POST, true);
|
||||
curl_setopt($c, CURLOPT_POSTFIELDS, $body);
|
||||
curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
curl_setopt($c, CURLOPT_WRITEFUNCTION,
|
||||
array(&$this, "_writeData"));
|
||||
|
||||
curl_exec($c);
|
||||
|
||||
$code = curl_getinfo($c, CURLINFO_HTTP_CODE);
|
||||
|
||||
if (!$code) {
|
||||
Auth_OpenID::log("Got no response code when fetching %s", $url);
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = $this->data;
|
||||
|
||||
curl_close($c);
|
||||
|
||||
$new_headers = $extra_headers;
|
||||
|
||||
foreach ($this->headers as $header) {
|
||||
if (strpos($header, ': ')) {
|
||||
list($name, $value) = explode(': ', $header, 2);
|
||||
$new_headers[$name] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Auth_OpenID::log("Successfully fetched '%s': POST response code %s",
|
||||
$url, $code);
|
||||
|
||||
return new Auth_Yadis_HTTPResponse($url, $code,
|
||||
$new_headers, $body);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
259
extlib/Auth/Yadis/ParseHTML.php
Normal file
259
extlib/Auth/Yadis/ParseHTML.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This is the HTML pseudo-parser for the Yadis library.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class is responsible for scanning an HTML string to find META
|
||||
* tags and their attributes. This is used by the Yadis discovery
|
||||
* process. This class must be instantiated to be used.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_ParseHTML {
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $_re_flags = "si";
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $_removed_re =
|
||||
"<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $_tag_expr = "<%s%s(?:\s.*?)?%s>";
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\/\s>]';
|
||||
|
||||
function Auth_Yadis_ParseHTML()
|
||||
{
|
||||
$this->_attr_find = sprintf("/%s/%s",
|
||||
$this->_attr_find,
|
||||
$this->_re_flags);
|
||||
|
||||
$this->_removed_re = sprintf("/%s/%s",
|
||||
$this->_removed_re,
|
||||
$this->_re_flags);
|
||||
|
||||
$this->_entity_replacements = array(
|
||||
'amp' => '&',
|
||||
'lt' => '<',
|
||||
'gt' => '>',
|
||||
'quot' => '"'
|
||||
);
|
||||
|
||||
$this->_ent_replace =
|
||||
sprintf("&(%s);", implode("|",
|
||||
$this->_entity_replacements));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace HTML entities (amp, lt, gt, and quot) as well as
|
||||
* numeric entities (e.g. #x9f;) with their actual values and
|
||||
* return the new string.
|
||||
*
|
||||
* @access private
|
||||
* @param string $str The string in which to look for entities
|
||||
* @return string $new_str The new string entities decoded
|
||||
*/
|
||||
function replaceEntities($str)
|
||||
{
|
||||
foreach ($this->_entity_replacements as $old => $new) {
|
||||
$str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
|
||||
}
|
||||
|
||||
// Replace numeric entities because html_entity_decode doesn't
|
||||
// do it for us.
|
||||
$str = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $str);
|
||||
$str = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $str);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip single and double quotes off of a string, if they are
|
||||
* present.
|
||||
*
|
||||
* @access private
|
||||
* @param string $str The original string
|
||||
* @return string $new_str The new string with leading and
|
||||
* trailing quotes removed
|
||||
*/
|
||||
function removeQuotes($str)
|
||||
{
|
||||
$matches = array();
|
||||
$double = '/^"(.*)"$/';
|
||||
$single = "/^\'(.*)\'$/";
|
||||
|
||||
if (preg_match($double, $str, $matches)) {
|
||||
return $matches[1];
|
||||
} else if (preg_match($single, $str, $matches)) {
|
||||
return $matches[1];
|
||||
} else {
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a regular expression that will match an opening
|
||||
* or closing tag from a set of names.
|
||||
*
|
||||
* @access private
|
||||
* @param mixed $tag_names Tag names to match
|
||||
* @param mixed $close false/0 = no, true/1 = yes, other = maybe
|
||||
* @param mixed $self_close false/0 = no, true/1 = yes, other = maybe
|
||||
* @return string $regex A regular expression string to be used
|
||||
* in, say, preg_match.
|
||||
*/
|
||||
function tagPattern($tag_names, $close, $self_close)
|
||||
{
|
||||
if (is_array($tag_names)) {
|
||||
$tag_names = '(?:'.implode('|',$tag_names).')';
|
||||
}
|
||||
if ($close) {
|
||||
$close = '\/' . (($close == 1)? '' : '?');
|
||||
} else {
|
||||
$close = '';
|
||||
}
|
||||
if ($self_close) {
|
||||
$self_close = '(?:\/\s*)' . (($self_close == 1)? '' : '?');
|
||||
} else {
|
||||
$self_close = '';
|
||||
}
|
||||
$expr = sprintf($this->_tag_expr, $close, $tag_names, $self_close);
|
||||
|
||||
return sprintf("/%s/%s", $expr, $this->_re_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an HTML document string, this finds all the META tags in
|
||||
* the document, provided they are found in the
|
||||
* <HTML><HEAD>...</HEAD> section of the document. The <HTML> tag
|
||||
* may be missing.
|
||||
*
|
||||
* @access private
|
||||
* @param string $html_string An HTMl document string
|
||||
* @return array $tag_list Array of tags; each tag is an array of
|
||||
* attribute -> value.
|
||||
*/
|
||||
function getMetaTags($html_string)
|
||||
{
|
||||
$html_string = preg_replace($this->_removed_re,
|
||||
"",
|
||||
$html_string);
|
||||
|
||||
$key_tags = array($this->tagPattern('html', false, false),
|
||||
$this->tagPattern('head', false, false),
|
||||
$this->tagPattern('head', true, false),
|
||||
$this->tagPattern('html', true, false),
|
||||
$this->tagPattern(array(
|
||||
'body', 'frameset', 'frame', 'p', 'div',
|
||||
'table','span','a'), 'maybe', 'maybe'));
|
||||
$key_tags_pos = array();
|
||||
foreach ($key_tags as $pat) {
|
||||
$matches = array();
|
||||
preg_match($pat, $html_string, $matches, PREG_OFFSET_CAPTURE);
|
||||
if($matches) {
|
||||
$key_tags_pos[] = $matches[0][1];
|
||||
} else {
|
||||
$key_tags_pos[] = null;
|
||||
}
|
||||
}
|
||||
// no opening head tag
|
||||
if (is_null($key_tags_pos[1])) {
|
||||
return array();
|
||||
}
|
||||
// the effective </head> is the min of the following
|
||||
if (is_null($key_tags_pos[2])) {
|
||||
$key_tags_pos[2] = strlen($html_string);
|
||||
}
|
||||
foreach (array($key_tags_pos[3], $key_tags_pos[4]) as $pos) {
|
||||
if (!is_null($pos) && $pos < $key_tags_pos[2]) {
|
||||
$key_tags_pos[2] = $pos;
|
||||
}
|
||||
}
|
||||
// closing head tag comes before opening head tag
|
||||
if ($key_tags_pos[1] > $key_tags_pos[2]) {
|
||||
return array();
|
||||
}
|
||||
// if there is an opening html tag, make sure the opening head tag
|
||||
// comes after it
|
||||
if (!is_null($key_tags_pos[0]) && $key_tags_pos[1] < $key_tags_pos[0]) {
|
||||
return array();
|
||||
}
|
||||
$html_string = substr($html_string, $key_tags_pos[1],
|
||||
($key_tags_pos[2]-$key_tags_pos[1]));
|
||||
|
||||
$link_data = array();
|
||||
$link_matches = array();
|
||||
|
||||
if (!preg_match_all($this->tagPattern('meta', false, 'maybe'),
|
||||
$html_string, $link_matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($link_matches[0] as $link) {
|
||||
$attr_matches = array();
|
||||
preg_match_all($this->_attr_find, $link, $attr_matches);
|
||||
$link_attrs = array();
|
||||
foreach ($attr_matches[0] as $index => $full_match) {
|
||||
$name = $attr_matches[1][$index];
|
||||
$value = $this->replaceEntities(
|
||||
$this->removeQuotes($attr_matches[2][$index]));
|
||||
|
||||
$link_attrs[strtolower($name)] = $value;
|
||||
}
|
||||
$link_data[] = $link_attrs;
|
||||
}
|
||||
|
||||
return $link_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for a META tag with an "http-equiv" attribute whose value
|
||||
* is one of ("x-xrds-location", "x-yadis-location"), ignoring
|
||||
* case. If such a META tag is found, its "content" attribute
|
||||
* value is returned.
|
||||
*
|
||||
* @param string $html_string An HTML document in string format
|
||||
* @return mixed $content The "content" attribute value of the
|
||||
* META tag, if found, or null if no such tag was found.
|
||||
*/
|
||||
function getHTTPEquiv($html_string)
|
||||
{
|
||||
$meta_tags = $this->getMetaTags($html_string);
|
||||
|
||||
if ($meta_tags) {
|
||||
foreach ($meta_tags as $tag) {
|
||||
if (array_key_exists('http-equiv', $tag) &&
|
||||
(in_array(strtolower($tag['http-equiv']),
|
||||
array('x-xrds-location', 'x-yadis-location'))) &&
|
||||
array_key_exists('content', $tag)) {
|
||||
return $tag['content'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
251
extlib/Auth/Yadis/PlainHTTPFetcher.php
Normal file
251
extlib/Auth/Yadis/PlainHTTPFetcher.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module contains the plain non-curl HTTP fetcher
|
||||
* implementation.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface import
|
||||
*/
|
||||
require_once "Auth/Yadis/HTTPFetcher.php";
|
||||
|
||||
/**
|
||||
* This class implements a plain, hand-built socket-based fetcher
|
||||
* which will be used in the event that CURL is unavailable.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
|
||||
/**
|
||||
* Does this fetcher support SSL URLs?
|
||||
*/
|
||||
function supportsSSL()
|
||||
{
|
||||
return function_exists('openssl_open');
|
||||
}
|
||||
|
||||
function get($url, $extra_headers = null)
|
||||
{
|
||||
if (!$this->canFetchURL($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$redir = true;
|
||||
|
||||
$stop = time() + $this->timeout;
|
||||
$off = $this->timeout;
|
||||
|
||||
while ($redir && ($off > 0)) {
|
||||
|
||||
$parts = parse_url($url);
|
||||
|
||||
$specify_port = true;
|
||||
|
||||
// Set a default port.
|
||||
if (!array_key_exists('port', $parts)) {
|
||||
$specify_port = false;
|
||||
if ($parts['scheme'] == 'http') {
|
||||
$parts['port'] = 80;
|
||||
} elseif ($parts['scheme'] == 'https') {
|
||||
$parts['port'] = 443;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists('path', $parts)) {
|
||||
$parts['path'] = '/';
|
||||
}
|
||||
|
||||
$host = $parts['host'];
|
||||
|
||||
if ($parts['scheme'] == 'https') {
|
||||
$host = 'ssl://' . $host;
|
||||
}
|
||||
|
||||
$user_agent = Auth_OpenID_USER_AGENT;
|
||||
|
||||
$headers = array(
|
||||
"GET ".$parts['path'].
|
||||
(array_key_exists('query', $parts) ?
|
||||
"?".$parts['query'] : "").
|
||||
" HTTP/1.0",
|
||||
"User-Agent: $user_agent",
|
||||
"Host: ".$parts['host'].
|
||||
($specify_port ? ":".$parts['port'] : ""),
|
||||
"Range: 0-".
|
||||
(1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB),
|
||||
"Port: ".$parts['port']);
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
|
||||
if ($extra_headers) {
|
||||
foreach ($extra_headers as $h) {
|
||||
$headers[] = $h;
|
||||
}
|
||||
}
|
||||
|
||||
@$sock = fsockopen($host, $parts['port'], $errno, $errstr,
|
||||
$this->timeout);
|
||||
if ($sock === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stream_set_timeout($sock, $this->timeout);
|
||||
|
||||
fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
|
||||
|
||||
$data = "";
|
||||
$kilobytes = 0;
|
||||
while (!feof($sock) &&
|
||||
$kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
|
||||
$data .= fgets($sock, 1024);
|
||||
$kilobytes += 1;
|
||||
}
|
||||
|
||||
fclose($sock);
|
||||
|
||||
// Split response into header and body sections
|
||||
list($headers, $body) = explode("\r\n\r\n", $data, 2);
|
||||
$headers = explode("\r\n", $headers);
|
||||
|
||||
$http_code = explode(" ", $headers[0]);
|
||||
$code = $http_code[1];
|
||||
|
||||
if (in_array($code, array('301', '302'))) {
|
||||
$url = $this->_findRedirect($headers);
|
||||
$redir = true;
|
||||
} else {
|
||||
$redir = false;
|
||||
}
|
||||
|
||||
$off = $stop - time();
|
||||
}
|
||||
|
||||
$new_headers = array();
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match("/:/", $header)) {
|
||||
$parts = explode(": ", $header, 2);
|
||||
|
||||
if (count($parts) == 2) {
|
||||
list($name, $value) = $parts;
|
||||
$new_headers[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Auth_Yadis_HTTPResponse($url, $code, $new_headers, $body);
|
||||
}
|
||||
|
||||
function post($url, $body, $extra_headers = null)
|
||||
{
|
||||
if (!$this->canFetchURL($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = parse_url($url);
|
||||
|
||||
$headers = array();
|
||||
|
||||
$post_path = $parts['path'];
|
||||
if (isset($parts['query'])) {
|
||||
$post_path .= '?' . $parts['query'];
|
||||
}
|
||||
|
||||
$headers[] = "POST ".$post_path." HTTP/1.0";
|
||||
$headers[] = "Host: " . $parts['host'];
|
||||
$headers[] = "Content-type: application/x-www-form-urlencoded";
|
||||
$headers[] = "Content-length: " . strval(strlen($body));
|
||||
|
||||
if ($extra_headers &&
|
||||
is_array($extra_headers)) {
|
||||
$headers = array_merge($headers, $extra_headers);
|
||||
}
|
||||
|
||||
// Join all headers together.
|
||||
$all_headers = implode("\r\n", $headers);
|
||||
|
||||
// Add headers, two newlines, and request body.
|
||||
$request = $all_headers . "\r\n\r\n" . $body;
|
||||
|
||||
// Set a default port.
|
||||
if (!array_key_exists('port', $parts)) {
|
||||
if ($parts['scheme'] == 'http') {
|
||||
$parts['port'] = 80;
|
||||
} elseif ($parts['scheme'] == 'https') {
|
||||
$parts['port'] = 443;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($parts['scheme'] == 'https') {
|
||||
$parts['host'] = sprintf("ssl://%s", $parts['host']);
|
||||
}
|
||||
|
||||
// Connect to the remote server.
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
|
||||
$sock = fsockopen($parts['host'], $parts['port'], $errno, $errstr,
|
||||
$this->timeout);
|
||||
|
||||
if ($sock === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
stream_set_timeout($sock, $this->timeout);
|
||||
|
||||
// Write the POST request.
|
||||
fputs($sock, $request);
|
||||
|
||||
// Get the response from the server.
|
||||
$response = "";
|
||||
while (!feof($sock)) {
|
||||
if ($data = fgets($sock, 128)) {
|
||||
$response .= $data;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Split the request into headers and body.
|
||||
list($headers, $response_body) = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
$headers = explode("\r\n", $headers);
|
||||
|
||||
// Expect the first line of the headers data to be something
|
||||
// like HTTP/1.1 200 OK. Split the line on spaces and take
|
||||
// the second token, which should be the return code.
|
||||
$http_code = explode(" ", $headers[0]);
|
||||
$code = $http_code[1];
|
||||
|
||||
$new_headers = array();
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match("/:/", $header)) {
|
||||
list($name, $value) = explode(": ", $header, 2);
|
||||
$new_headers[$name] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Auth_Yadis_HTTPResponse($url, $code,
|
||||
$new_headers, $response_body);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
374
extlib/Auth/Yadis/XML.php
Normal file
374
extlib/Auth/Yadis/XML.php
Normal file
@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
|
||||
* and 5, respectively.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
|
||||
/**
|
||||
* The base class for wrappers for available PHP XML-parsing
|
||||
* extensions. To work with this Yadis library, subclasses of this
|
||||
* class MUST implement the API as defined in the remarks for this
|
||||
* class. Subclasses of Auth_Yadis_XMLParser are used to wrap
|
||||
* particular PHP XML extensions such as 'domxml'. These are used
|
||||
* internally by the library depending on the availability of
|
||||
* supported PHP XML extensions.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_XMLParser {
|
||||
/**
|
||||
* Initialize an instance of Auth_Yadis_XMLParser with some
|
||||
* XML and namespaces. This SHOULD NOT be overridden by
|
||||
* subclasses.
|
||||
*
|
||||
* @param string $xml_string A string of XML to be parsed.
|
||||
* @param array $namespace_map An array of ($ns_name => $ns_uri)
|
||||
* to be registered with the XML parser. May be empty.
|
||||
* @return boolean $result True if the initialization and
|
||||
* namespace registration(s) succeeded; false otherwise.
|
||||
*/
|
||||
function init($xml_string, $namespace_map)
|
||||
{
|
||||
if (!$this->setXML($xml_string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($namespace_map as $prefix => $uri) {
|
||||
if (!$this->registerNamespace($prefix, $uri)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a namespace with the XML parser. This should be
|
||||
* overridden by subclasses.
|
||||
*
|
||||
* @param string $prefix The namespace prefix to appear in XML tag
|
||||
* names.
|
||||
*
|
||||
* @param string $uri The namespace URI to be used to identify the
|
||||
* namespace in the XML.
|
||||
*
|
||||
* @return boolean $result True if the registration succeeded;
|
||||
* false otherwise.
|
||||
*/
|
||||
function registerNamespace($prefix, $uri)
|
||||
{
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this parser object's XML payload. This should be
|
||||
* overridden by subclasses.
|
||||
*
|
||||
* @param string $xml_string The XML string to pass to this
|
||||
* object's XML parser.
|
||||
*
|
||||
* @return boolean $result True if the initialization succeeded;
|
||||
* false otherwise.
|
||||
*/
|
||||
function setXML($xml_string)
|
||||
{
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an XPath expression and return the resulting node
|
||||
* list. This should be overridden by subclasses.
|
||||
*
|
||||
* @param string $xpath The XPath expression to be evaluated.
|
||||
*
|
||||
* @param mixed $node A node object resulting from a previous
|
||||
* evalXPath call. This node, if specified, provides the context
|
||||
* for the evaluation of this xpath expression.
|
||||
*
|
||||
* @return array $node_list An array of matching opaque node
|
||||
* objects to be used with other methods of this parser class.
|
||||
*/
|
||||
function evalXPath($xpath, $node = null)
|
||||
{
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the textual content of a specified node.
|
||||
*
|
||||
* @param mixed $node A node object from a previous call to
|
||||
* $this->evalXPath().
|
||||
*
|
||||
* @return string $content The content of this node.
|
||||
*/
|
||||
function content($node)
|
||||
{
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attributes of a specified node.
|
||||
*
|
||||
* @param mixed $node A node object from a previous call to
|
||||
* $this->evalXPath().
|
||||
*
|
||||
* @return array $attrs An array mapping attribute names to
|
||||
* values.
|
||||
*/
|
||||
function attributes($node)
|
||||
{
|
||||
// Not implemented.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This concrete implementation of Auth_Yadis_XMLParser implements
|
||||
* the appropriate API for the 'domxml' extension which is typically
|
||||
* packaged with PHP 4. This class will be used whenever the 'domxml'
|
||||
* extension is detected. See the Auth_Yadis_XMLParser class for
|
||||
* details on this class's methods.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_domxml extends Auth_Yadis_XMLParser {
|
||||
function Auth_Yadis_domxml()
|
||||
{
|
||||
$this->xml = null;
|
||||
$this->doc = null;
|
||||
$this->xpath = null;
|
||||
$this->errors = array();
|
||||
}
|
||||
|
||||
function setXML($xml_string)
|
||||
{
|
||||
$this->xml = $xml_string;
|
||||
$this->doc = @domxml_open_mem($xml_string, DOMXML_LOAD_PARSING,
|
||||
$this->errors);
|
||||
|
||||
if (!$this->doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->xpath = $this->doc->xpath_new_context();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function registerNamespace($prefix, $uri)
|
||||
{
|
||||
return xpath_register_ns($this->xpath, $prefix, $uri);
|
||||
}
|
||||
|
||||
function &evalXPath($xpath, $node = null)
|
||||
{
|
||||
if ($node) {
|
||||
$result = @$this->xpath->xpath_eval($xpath, $node);
|
||||
} else {
|
||||
$result = @$this->xpath->xpath_eval($xpath);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
$n = array();
|
||||
return $n;
|
||||
}
|
||||
|
||||
if (!$result->nodeset) {
|
||||
$n = array();
|
||||
return $n;
|
||||
}
|
||||
|
||||
return $result->nodeset;
|
||||
}
|
||||
|
||||
function content($node)
|
||||
{
|
||||
if ($node) {
|
||||
return $node->get_content();
|
||||
}
|
||||
}
|
||||
|
||||
function attributes($node)
|
||||
{
|
||||
if ($node) {
|
||||
$arr = $node->attributes();
|
||||
$result = array();
|
||||
|
||||
if ($arr) {
|
||||
foreach ($arr as $attrnode) {
|
||||
$result[$attrnode->name] = $attrnode->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This concrete implementation of Auth_Yadis_XMLParser implements
|
||||
* the appropriate API for the 'dom' extension which is typically
|
||||
* packaged with PHP 5. This class will be used whenever the 'dom'
|
||||
* extension is detected. See the Auth_Yadis_XMLParser class for
|
||||
* details on this class's methods.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_dom extends Auth_Yadis_XMLParser {
|
||||
function Auth_Yadis_dom()
|
||||
{
|
||||
$this->xml = null;
|
||||
$this->doc = null;
|
||||
$this->xpath = null;
|
||||
$this->errors = array();
|
||||
}
|
||||
|
||||
function setXML($xml_string)
|
||||
{
|
||||
$this->xml = $xml_string;
|
||||
$this->doc = new DOMDocument;
|
||||
|
||||
if (!$this->doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!@$this->doc->loadXML($xml_string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->xpath = new DOMXPath($this->doc);
|
||||
|
||||
if ($this->xpath) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function registerNamespace($prefix, $uri)
|
||||
{
|
||||
return $this->xpath->registerNamespace($prefix, $uri);
|
||||
}
|
||||
|
||||
function &evalXPath($xpath, $node = null)
|
||||
{
|
||||
if ($node) {
|
||||
$result = @$this->xpath->query($xpath, $node);
|
||||
} else {
|
||||
$result = @$this->xpath->query($xpath);
|
||||
}
|
||||
|
||||
$n = array();
|
||||
|
||||
if (!$result) {
|
||||
return $n;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $result->length; $i++) {
|
||||
$n[] = $result->item($i);
|
||||
}
|
||||
|
||||
return $n;
|
||||
}
|
||||
|
||||
function content($node)
|
||||
{
|
||||
if ($node) {
|
||||
return $node->textContent;
|
||||
}
|
||||
}
|
||||
|
||||
function attributes($node)
|
||||
{
|
||||
if ($node) {
|
||||
$arr = $node->attributes;
|
||||
$result = array();
|
||||
|
||||
if ($arr) {
|
||||
for ($i = 0; $i < $arr->length; $i++) {
|
||||
$node = $arr->item($i);
|
||||
$result[$node->nodeName] = $node->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global $__Auth_Yadis_defaultParser;
|
||||
$__Auth_Yadis_defaultParser = null;
|
||||
|
||||
/**
|
||||
* Set a default parser to override the extension-driven selection of
|
||||
* available parser classes. This is helpful in a test environment or
|
||||
* one in which multiple parsers can be used but one is more
|
||||
* desirable.
|
||||
*
|
||||
* @param Auth_Yadis_XMLParser $parser An instance of a
|
||||
* Auth_Yadis_XMLParser subclass.
|
||||
*/
|
||||
function Auth_Yadis_setDefaultParser(&$parser)
|
||||
{
|
||||
global $__Auth_Yadis_defaultParser;
|
||||
$__Auth_Yadis_defaultParser =& $parser;
|
||||
}
|
||||
|
||||
function Auth_Yadis_getSupportedExtensions()
|
||||
{
|
||||
return array(
|
||||
'dom' => array('classname' => 'Auth_Yadis_dom',
|
||||
'libname' => array('dom.so', 'dom.dll')),
|
||||
'domxml' => array('classname' => 'Auth_Yadis_domxml',
|
||||
'libname' => array('domxml.so', 'php_domxml.dll')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of a Auth_Yadis_XMLParser subclass based on
|
||||
* the availability of PHP extensions for XML parsing. If
|
||||
* Auth_Yadis_setDefaultParser has been called, the parser used in
|
||||
* that call will be returned instead.
|
||||
*/
|
||||
function &Auth_Yadis_getXMLParser()
|
||||
{
|
||||
global $__Auth_Yadis_defaultParser;
|
||||
|
||||
if (isset($__Auth_Yadis_defaultParser)) {
|
||||
return $__Auth_Yadis_defaultParser;
|
||||
}
|
||||
|
||||
$p = null;
|
||||
$classname = null;
|
||||
|
||||
$extensions = Auth_Yadis_getSupportedExtensions();
|
||||
|
||||
// Return a wrapper for the resident implementation, if any.
|
||||
foreach ($extensions as $name => $params) {
|
||||
if (!extension_loaded($name)) {
|
||||
foreach ($params['libname'] as $libname) {
|
||||
if (@dl($libname)) {
|
||||
$classname = $params['classname'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$classname = $params['classname'];
|
||||
}
|
||||
if (isset($classname)) {
|
||||
$p = new $classname();
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($p)) {
|
||||
trigger_error('No XML parser was found', E_USER_ERROR);
|
||||
} else {
|
||||
Auth_Yadis_setDefaultParser($p);
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
?>
|
478
extlib/Auth/Yadis/XRDS.php
Normal file
478
extlib/Auth/Yadis/XRDS.php
Normal file
@ -0,0 +1,478 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This module contains the XRDS parsing code.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require the XPath implementation.
|
||||
*/
|
||||
require_once 'Auth/Yadis/XML.php';
|
||||
|
||||
/**
|
||||
* This match mode means a given service must match ALL filters passed
|
||||
* to the Auth_Yadis_XRDS::services() call.
|
||||
*/
|
||||
define('SERVICES_YADIS_MATCH_ALL', 101);
|
||||
|
||||
/**
|
||||
* This match mode means a given service must match ANY filters (at
|
||||
* least one) passed to the Auth_Yadis_XRDS::services() call.
|
||||
*/
|
||||
define('SERVICES_YADIS_MATCH_ANY', 102);
|
||||
|
||||
/**
|
||||
* The priority value used for service elements with no priority
|
||||
* specified.
|
||||
*/
|
||||
define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
|
||||
|
||||
/**
|
||||
* XRD XML namespace
|
||||
*/
|
||||
define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
|
||||
|
||||
/**
|
||||
* XRDS XML namespace
|
||||
*/
|
||||
define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
|
||||
|
||||
function Auth_Yadis_getNSMap()
|
||||
{
|
||||
return array('xrds' => Auth_Yadis_XMLNS_XRDS,
|
||||
'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function Auth_Yadis_array_scramble($arr)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
while (count($arr)) {
|
||||
$index = array_rand($arr, 1);
|
||||
$result[] = $arr[$index];
|
||||
unset($arr[$index]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a <Service> element in an XRDS document.
|
||||
* Objects of this type are returned by
|
||||
* Auth_Yadis_XRDS::services() and
|
||||
* Auth_Yadis_Yadis::services(). Each object corresponds directly
|
||||
* to a <Service> element in the XRDS and supplies a
|
||||
* getElements($name) method which you should use to inspect the
|
||||
* element's contents. See {@link Auth_Yadis_Yadis} for more
|
||||
* information on the role this class plays in Yadis discovery.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_Service {
|
||||
|
||||
/**
|
||||
* Creates an empty service object.
|
||||
*/
|
||||
function Auth_Yadis_Service()
|
||||
{
|
||||
$this->element = null;
|
||||
$this->parser = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URIs in the "Type" elements, if any, of this Service
|
||||
* element.
|
||||
*
|
||||
* @return array $type_uris An array of Type URI strings.
|
||||
*/
|
||||
function getTypes()
|
||||
{
|
||||
$t = array();
|
||||
foreach ($this->getElements('xrd:Type') as $elem) {
|
||||
$c = $this->parser->content($elem);
|
||||
if ($c) {
|
||||
$t[] = $c;
|
||||
}
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
function matchTypes($type_uris)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($this->getTypes() as $typ) {
|
||||
if (in_array($typ, $type_uris)) {
|
||||
$result[] = $typ;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URIs in the "URI" elements, if any, of this Service
|
||||
* element. The URIs are returned sorted in priority order.
|
||||
*
|
||||
* @return array $uris An array of URI strings.
|
||||
*/
|
||||
function getURIs()
|
||||
{
|
||||
$uris = array();
|
||||
$last = array();
|
||||
|
||||
foreach ($this->getElements('xrd:URI') as $elem) {
|
||||
$uri_string = $this->parser->content($elem);
|
||||
$attrs = $this->parser->attributes($elem);
|
||||
if ($attrs &&
|
||||
array_key_exists('priority', $attrs)) {
|
||||
$priority = intval($attrs['priority']);
|
||||
if (!array_key_exists($priority, $uris)) {
|
||||
$uris[$priority] = array();
|
||||
}
|
||||
|
||||
$uris[$priority][] = $uri_string;
|
||||
} else {
|
||||
$last[] = $uri_string;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array_keys($uris);
|
||||
sort($keys);
|
||||
|
||||
// Rebuild array of URIs.
|
||||
$result = array();
|
||||
foreach ($keys as $k) {
|
||||
$new_uris = Auth_Yadis_array_scramble($uris[$k]);
|
||||
$result = array_merge($result, $new_uris);
|
||||
}
|
||||
|
||||
$result = array_merge($result,
|
||||
Auth_Yadis_array_scramble($last));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "priority" attribute value of this <Service>
|
||||
* element, if the attribute is present. Returns null if not.
|
||||
*
|
||||
* @return mixed $result Null or integer, depending on whether
|
||||
* this Service element has a 'priority' attribute.
|
||||
*/
|
||||
function getPriority()
|
||||
{
|
||||
$attributes = $this->parser->attributes($this->element);
|
||||
|
||||
if (array_key_exists('priority', $attributes)) {
|
||||
return intval($attributes['priority']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get XML elements from this object's <Service> element.
|
||||
*
|
||||
* This is what you should use to get all custom information out
|
||||
* of this element. This is used by service filter functions to
|
||||
* determine whether a service element contains specific tags,
|
||||
* etc. NOTE: this only considers elements which are direct
|
||||
* children of the <Service> element for this object.
|
||||
*
|
||||
* @param string $name The name of the element to look for
|
||||
* @return array $list An array of elements with the specified
|
||||
* name which are direct children of the <Service> element. The
|
||||
* nodes returned by this function can be passed to $this->parser
|
||||
* methods (see {@link Auth_Yadis_XMLParser}).
|
||||
*/
|
||||
function getElements($name)
|
||||
{
|
||||
return $this->parser->evalXPath($name, $this->element);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the expiration date of this XRD element, or None if no
|
||||
* expiration was specified.
|
||||
*
|
||||
* @param $default The value to use as the expiration if no expiration
|
||||
* was specified in the XRD.
|
||||
*/
|
||||
function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
|
||||
{
|
||||
$expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
|
||||
if ($expires_element === null) {
|
||||
return $default;
|
||||
} else {
|
||||
$expires_string = $expires_element->text;
|
||||
|
||||
// Will raise ValueError if the string is not the expected
|
||||
// format
|
||||
$t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
|
||||
|
||||
if ($t === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// [int $hour [, int $minute [, int $second [,
|
||||
// int $month [, int $day [, int $year ]]]]]]
|
||||
return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
|
||||
$t['tm_mon'], $t['tm_day'], $t['tm_year']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class performs parsing of XRDS documents.
|
||||
*
|
||||
* You should not instantiate this class directly; rather, call
|
||||
* parseXRDS statically:
|
||||
*
|
||||
* <pre> $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
|
||||
*
|
||||
* If the XRDS can be parsed and is valid, an instance of
|
||||
* Auth_Yadis_XRDS will be returned. Otherwise, null will be
|
||||
* returned. This class is used by the Auth_Yadis_Yadis::discover
|
||||
* method.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_XRDS {
|
||||
|
||||
/**
|
||||
* Instantiate a Auth_Yadis_XRDS object. Requires an XPath
|
||||
* instance which has been used to parse a valid XRDS document.
|
||||
*/
|
||||
function Auth_Yadis_XRDS(&$xmlParser, &$xrdNodes)
|
||||
{
|
||||
$this->parser =& $xmlParser;
|
||||
$this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
|
||||
$this->allXrdNodes =& $xrdNodes;
|
||||
$this->serviceList = array();
|
||||
$this->_parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an XML string (XRDS document) and return either a
|
||||
* Auth_Yadis_XRDS object or null, depending on whether the
|
||||
* XRDS XML is valid.
|
||||
*
|
||||
* @param string $xml_string An XRDS XML string.
|
||||
* @return mixed $xrds An instance of Auth_Yadis_XRDS or null,
|
||||
* depending on the validity of $xml_string
|
||||
*/
|
||||
function &parseXRDS($xml_string, $extra_ns_map = null)
|
||||
{
|
||||
$_null = null;
|
||||
|
||||
if (!$xml_string) {
|
||||
return $_null;
|
||||
}
|
||||
|
||||
$parser = Auth_Yadis_getXMLParser();
|
||||
|
||||
$ns_map = Auth_Yadis_getNSMap();
|
||||
|
||||
if ($extra_ns_map && is_array($extra_ns_map)) {
|
||||
$ns_map = array_merge($ns_map, $extra_ns_map);
|
||||
}
|
||||
|
||||
if (!($parser && $parser->init($xml_string, $ns_map))) {
|
||||
return $_null;
|
||||
}
|
||||
|
||||
// Try to get root element.
|
||||
$root = $parser->evalXPath('/xrds:XRDS[1]');
|
||||
if (!$root) {
|
||||
return $_null;
|
||||
}
|
||||
|
||||
if (is_array($root)) {
|
||||
$root = $root[0];
|
||||
}
|
||||
|
||||
$attrs = $parser->attributes($root);
|
||||
|
||||
if (array_key_exists('xmlns:xrd', $attrs) &&
|
||||
$attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
|
||||
return $_null;
|
||||
} else if (array_key_exists('xmlns', $attrs) &&
|
||||
preg_match('/xri/', $attrs['xmlns']) &&
|
||||
$attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
|
||||
return $_null;
|
||||
}
|
||||
|
||||
// Get the last XRD node.
|
||||
$xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
|
||||
|
||||
if (!$xrd_nodes) {
|
||||
return $_null;
|
||||
}
|
||||
|
||||
$xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
|
||||
return $xrds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _addService($priority, $service)
|
||||
{
|
||||
$priority = intval($priority);
|
||||
|
||||
if (!array_key_exists($priority, $this->serviceList)) {
|
||||
$this->serviceList[$priority] = array();
|
||||
}
|
||||
|
||||
$this->serviceList[$priority][] = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the service list using nodes from the XRDS XML
|
||||
* document.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _parse()
|
||||
{
|
||||
$this->serviceList = array();
|
||||
|
||||
$services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
|
||||
|
||||
foreach ($services as $node) {
|
||||
$s =& new Auth_Yadis_Service();
|
||||
$s->element = $node;
|
||||
$s->parser =& $this->parser;
|
||||
|
||||
$priority = $s->getPriority();
|
||||
|
||||
if ($priority === null) {
|
||||
$priority = SERVICES_YADIS_MAX_PRIORITY;
|
||||
}
|
||||
|
||||
$this->_addService($priority, $s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of service objects which correspond to <Service>
|
||||
* elements in the XRDS XML document for this object.
|
||||
*
|
||||
* Optionally, an array of filter callbacks may be given to limit
|
||||
* the list of returned service objects. Furthermore, the default
|
||||
* mode is to return all service objects which match ANY of the
|
||||
* specified filters, but $filter_mode may be
|
||||
* SERVICES_YADIS_MATCH_ALL if you want to be sure that the
|
||||
* returned services match all the given filters. See {@link
|
||||
* Auth_Yadis_Yadis} for detailed usage information on filter
|
||||
* functions.
|
||||
*
|
||||
* @param mixed $filters An array of callbacks to filter the
|
||||
* returned services, or null if all services are to be returned.
|
||||
* @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
|
||||
* SERVICES_YADIS_MATCH_ANY, depending on whether the returned
|
||||
* services should match ALL or ANY of the specified filters,
|
||||
* respectively.
|
||||
* @return mixed $services An array of {@link
|
||||
* Auth_Yadis_Service} objects if $filter_mode is a valid
|
||||
* mode; null if $filter_mode is an invalid mode (i.e., not
|
||||
* SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
|
||||
*/
|
||||
function services($filters = null,
|
||||
$filter_mode = SERVICES_YADIS_MATCH_ANY)
|
||||
{
|
||||
|
||||
$pri_keys = array_keys($this->serviceList);
|
||||
sort($pri_keys, SORT_NUMERIC);
|
||||
|
||||
// If no filters are specified, return the entire service
|
||||
// list, ordered by priority.
|
||||
if (!$filters ||
|
||||
(!is_array($filters))) {
|
||||
|
||||
$result = array();
|
||||
foreach ($pri_keys as $pri) {
|
||||
$result = array_merge($result, $this->serviceList[$pri]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// If a bad filter mode is specified, return null.
|
||||
if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
|
||||
SERVICES_YADIS_MATCH_ALL))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, use the callbacks in the filter list to
|
||||
// determine which services are returned.
|
||||
$filtered = array();
|
||||
|
||||
foreach ($pri_keys as $priority_value) {
|
||||
$service_obj_list = $this->serviceList[$priority_value];
|
||||
|
||||
foreach ($service_obj_list as $service) {
|
||||
|
||||
$matches = 0;
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
if (call_user_func_array($filter, array($service))) {
|
||||
$matches++;
|
||||
|
||||
if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
|
||||
$pri = $service->getPriority();
|
||||
if ($pri === null) {
|
||||
$pri = SERVICES_YADIS_MAX_PRIORITY;
|
||||
}
|
||||
|
||||
if (!array_key_exists($pri, $filtered)) {
|
||||
$filtered[$pri] = array();
|
||||
}
|
||||
|
||||
$filtered[$pri][] = $service;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
|
||||
($matches == count($filters))) {
|
||||
|
||||
$pri = $service->getPriority();
|
||||
if ($pri === null) {
|
||||
$pri = SERVICES_YADIS_MAX_PRIORITY;
|
||||
}
|
||||
|
||||
if (!array_key_exists($pri, $filtered)) {
|
||||
$filtered[$pri] = array();
|
||||
}
|
||||
$filtered[$pri][] = $service;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pri_keys = array_keys($filtered);
|
||||
sort($pri_keys, SORT_NUMERIC);
|
||||
|
||||
$result = array();
|
||||
foreach ($pri_keys as $pri) {
|
||||
$result = array_merge($result, $filtered[$pri]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
234
extlib/Auth/Yadis/XRI.php
Normal file
234
extlib/Auth/Yadis/XRI.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routines for XRI resolution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
require_once 'Auth/Yadis/Misc.php';
|
||||
require_once 'Auth/Yadis/Yadis.php';
|
||||
require_once 'Auth/OpenID.php';
|
||||
|
||||
function Auth_Yadis_getDefaultProxy()
|
||||
{
|
||||
return 'http://xri.net/';
|
||||
}
|
||||
|
||||
function Auth_Yadis_getXRIAuthorities()
|
||||
{
|
||||
return array('!', '=', '@', '+', '$', '(');
|
||||
}
|
||||
|
||||
function Auth_Yadis_getEscapeRE()
|
||||
{
|
||||
$parts = array();
|
||||
foreach (array_merge(Auth_Yadis_getUCSChars(),
|
||||
Auth_Yadis_getIPrivateChars()) as $pair) {
|
||||
list($m, $n) = $pair;
|
||||
$parts[] = sprintf("%s-%s", chr($m), chr($n));
|
||||
}
|
||||
|
||||
return sprintf('/[%s]/', implode('', $parts));
|
||||
}
|
||||
|
||||
function Auth_Yadis_getXrefRE()
|
||||
{
|
||||
return '/\((.*?)\)/';
|
||||
}
|
||||
|
||||
function Auth_Yadis_identifierScheme($identifier)
|
||||
{
|
||||
if (Auth_Yadis_startswith($identifier, 'xri://') ||
|
||||
($identifier &&
|
||||
in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
|
||||
return "XRI";
|
||||
} else {
|
||||
return "URI";
|
||||
}
|
||||
}
|
||||
|
||||
function Auth_Yadis_toIRINormal($xri)
|
||||
{
|
||||
if (!Auth_Yadis_startswith($xri, 'xri://')) {
|
||||
$xri = 'xri://' . $xri;
|
||||
}
|
||||
|
||||
return Auth_Yadis_escapeForIRI($xri);
|
||||
}
|
||||
|
||||
function _escape_xref($xref_match)
|
||||
{
|
||||
$xref = $xref_match[0];
|
||||
$xref = str_replace('/', '%2F', $xref);
|
||||
$xref = str_replace('?', '%3F', $xref);
|
||||
$xref = str_replace('#', '%23', $xref);
|
||||
return $xref;
|
||||
}
|
||||
|
||||
function Auth_Yadis_escapeForIRI($xri)
|
||||
{
|
||||
$xri = str_replace('%', '%25', $xri);
|
||||
$xri = preg_replace_callback(Auth_Yadis_getXrefRE(),
|
||||
'_escape_xref', $xri);
|
||||
return $xri;
|
||||
}
|
||||
|
||||
function Auth_Yadis_toURINormal($xri)
|
||||
{
|
||||
return Auth_Yadis_iriToURI(Auth_Yadis_toIRINormal($xri));
|
||||
}
|
||||
|
||||
function Auth_Yadis_iriToURI($iri)
|
||||
{
|
||||
if (1) {
|
||||
return $iri;
|
||||
} else {
|
||||
// According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
|
||||
return preg_replace_callback(Auth_Yadis_getEscapeRE(),
|
||||
'Auth_Yadis_pct_escape_unicode', $iri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Auth_Yadis_XRIAppendArgs($url, $args)
|
||||
{
|
||||
// Append some arguments to an HTTP query. Yes, this is just like
|
||||
// OpenID's appendArgs, but with special seasoning for XRI
|
||||
// queries.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// According to XRI Resolution section "QXRI query parameters":
|
||||
//
|
||||
// "If the original QXRI had a null query component (only a
|
||||
// leading question mark), or a query component consisting of
|
||||
// only question marks, one additional leading question mark MUST
|
||||
// be added when adding any XRI resolution parameters."
|
||||
if (strpos(rtrim($url, '?'), '?') !== false) {
|
||||
$sep = '&';
|
||||
} else {
|
||||
$sep = '?';
|
||||
}
|
||||
|
||||
return $url . $sep . Auth_OpenID::httpBuildQuery($args);
|
||||
}
|
||||
|
||||
function Auth_Yadis_providerIsAuthoritative($providerID, $canonicalID)
|
||||
{
|
||||
$lastbang = strrpos($canonicalID, '!');
|
||||
$p = substr($canonicalID, 0, $lastbang);
|
||||
return $p == $providerID;
|
||||
}
|
||||
|
||||
function Auth_Yadis_rootAuthority($xri)
|
||||
{
|
||||
// Return the root authority for an XRI.
|
||||
|
||||
$root = null;
|
||||
|
||||
if (Auth_Yadis_startswith($xri, 'xri://')) {
|
||||
$xri = substr($xri, 6);
|
||||
}
|
||||
|
||||
$authority = explode('/', $xri, 2);
|
||||
$authority = $authority[0];
|
||||
if ($authority[0] == '(') {
|
||||
// Cross-reference.
|
||||
// XXX: This is incorrect if someone nests cross-references so
|
||||
// there is another close-paren in there. Hopefully nobody
|
||||
// does that before we have a real xriparse function.
|
||||
// Hopefully nobody does that *ever*.
|
||||
$root = substr($authority, 0, strpos($authority, ')') + 1);
|
||||
} else if (in_array($authority[0], Auth_Yadis_getXRIAuthorities())) {
|
||||
// Other XRI reference.
|
||||
$root = $authority[0];
|
||||
} else {
|
||||
// IRI reference.
|
||||
$_segments = explode("!", $authority);
|
||||
$segments = array();
|
||||
foreach ($_segments as $s) {
|
||||
$segments = array_merge($segments, explode("*", $s));
|
||||
}
|
||||
$root = $segments[0];
|
||||
}
|
||||
|
||||
return Auth_Yadis_XRI($root);
|
||||
}
|
||||
|
||||
function Auth_Yadis_XRI($xri)
|
||||
{
|
||||
if (!Auth_Yadis_startswith($xri, 'xri://')) {
|
||||
$xri = 'xri://' . $xri;
|
||||
}
|
||||
return $xri;
|
||||
}
|
||||
|
||||
function Auth_Yadis_getCanonicalID($iname, $xrds)
|
||||
{
|
||||
// Returns false or a canonical ID value.
|
||||
|
||||
// Now nodes are in reverse order.
|
||||
$xrd_list = array_reverse($xrds->allXrdNodes);
|
||||
$parser =& $xrds->parser;
|
||||
$node = $xrd_list[0];
|
||||
|
||||
$canonicalID_nodes = $parser->evalXPath('xrd:CanonicalID', $node);
|
||||
|
||||
if (!$canonicalID_nodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$canonicalID = $canonicalID_nodes[0];
|
||||
$canonicalID = Auth_Yadis_XRI($parser->content($canonicalID));
|
||||
|
||||
$childID = $canonicalID;
|
||||
|
||||
for ($i = 1; $i < count($xrd_list); $i++) {
|
||||
$xrd = $xrd_list[$i];
|
||||
|
||||
$parent_sought = substr($childID, 0, strrpos($childID, '!'));
|
||||
$parentCID = $parser->evalXPath('xrd:CanonicalID', $xrd);
|
||||
if (!$parentCID) {
|
||||
return false;
|
||||
}
|
||||
$parentCID = Auth_Yadis_XRI($parser->content($parentCID[0]));
|
||||
|
||||
if (strcasecmp($parent_sought, $parentCID)) {
|
||||
// raise XRDSFraud.
|
||||
return false;
|
||||
}
|
||||
|
||||
$childID = $parent_sought;
|
||||
}
|
||||
|
||||
$root = Auth_Yadis_rootAuthority($iname);
|
||||
if (!Auth_Yadis_providerIsAuthoritative($root, $childID)) {
|
||||
// raise XRDSFraud.
|
||||
return false;
|
||||
}
|
||||
|
||||
return $canonicalID;
|
||||
}
|
||||
|
||||
?>
|
72
extlib/Auth/Yadis/XRIRes.php
Normal file
72
extlib/Auth/Yadis/XRIRes.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Code for using a proxy XRI resolver.
|
||||
*/
|
||||
|
||||
require_once 'Auth/Yadis/XRDS.php';
|
||||
require_once 'Auth/Yadis/XRI.php';
|
||||
|
||||
class Auth_Yadis_ProxyResolver {
|
||||
function Auth_Yadis_ProxyResolver(&$fetcher, $proxy_url = null)
|
||||
{
|
||||
$this->fetcher =& $fetcher;
|
||||
$this->proxy_url = $proxy_url;
|
||||
if (!$this->proxy_url) {
|
||||
$this->proxy_url = Auth_Yadis_getDefaultProxy();
|
||||
}
|
||||
}
|
||||
|
||||
function queryURL($xri, $service_type = null)
|
||||
{
|
||||
// trim off the xri:// prefix
|
||||
$qxri = substr(Auth_Yadis_toURINormal($xri), 6);
|
||||
$hxri = $this->proxy_url . $qxri;
|
||||
$args = array(
|
||||
'_xrd_r' => 'application/xrds+xml'
|
||||
);
|
||||
|
||||
if ($service_type) {
|
||||
$args['_xrd_t'] = $service_type;
|
||||
} else {
|
||||
// Don't perform service endpoint selection.
|
||||
$args['_xrd_r'] .= ';sep=false';
|
||||
}
|
||||
|
||||
$query = Auth_Yadis_XRIAppendArgs($hxri, $args);
|
||||
return $query;
|
||||
}
|
||||
|
||||
function query($xri, $service_types, $filters = array())
|
||||
{
|
||||
$services = array();
|
||||
$canonicalID = null;
|
||||
foreach ($service_types as $service_type) {
|
||||
$url = $this->queryURL($xri, $service_type);
|
||||
$response = $this->fetcher->get($url);
|
||||
if ($response->status != 200 and $response->status != 206) {
|
||||
continue;
|
||||
}
|
||||
$xrds = Auth_Yadis_XRDS::parseXRDS($response->body);
|
||||
if (!$xrds) {
|
||||
continue;
|
||||
}
|
||||
$canonicalID = Auth_Yadis_getCanonicalID($xri,
|
||||
$xrds);
|
||||
|
||||
if ($canonicalID === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$some_services = $xrds->services($filters);
|
||||
$services = array_merge($services, $some_services);
|
||||
// TODO:
|
||||
// * If we do get hits for multiple service_types, we're
|
||||
// almost certainly going to have duplicated service
|
||||
// entries and broken priority ordering.
|
||||
}
|
||||
return array($canonicalID, $services);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
382
extlib/Auth/Yadis/Yadis.php
Normal file
382
extlib/Auth/Yadis/Yadis.php
Normal file
@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The core PHP Yadis implementation.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: See the COPYING file included in this distribution.
|
||||
*
|
||||
* @package OpenID
|
||||
* @author JanRain, Inc. <openid@janrain.com>
|
||||
* @copyright 2005-2008 Janrain, Inc.
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Need both fetcher types so we can use the right one based on the
|
||||
* presence or absence of CURL.
|
||||
*/
|
||||
require_once "Auth/Yadis/PlainHTTPFetcher.php";
|
||||
require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
|
||||
|
||||
/**
|
||||
* Need this for parsing HTML (looking for META tags).
|
||||
*/
|
||||
require_once "Auth/Yadis/ParseHTML.php";
|
||||
|
||||
/**
|
||||
* Need this to parse the XRDS document during Yadis discovery.
|
||||
*/
|
||||
require_once "Auth/Yadis/XRDS.php";
|
||||
|
||||
/**
|
||||
* XRDS (yadis) content type
|
||||
*/
|
||||
define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
|
||||
|
||||
/**
|
||||
* Yadis header
|
||||
*/
|
||||
define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
|
||||
|
||||
/**
|
||||
* Contains the result of performing Yadis discovery on a URI.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_DiscoveryResult {
|
||||
|
||||
// The URI that was passed to the fetcher
|
||||
var $request_uri = null;
|
||||
|
||||
// The result of following redirects from the request_uri
|
||||
var $normalized_uri = null;
|
||||
|
||||
// The URI from which the response text was returned (set to
|
||||
// None if there was no XRDS document found)
|
||||
var $xrds_uri = null;
|
||||
|
||||
var $xrds = null;
|
||||
|
||||
// The content-type returned with the response_text
|
||||
var $content_type = null;
|
||||
|
||||
// The document returned from the xrds_uri
|
||||
var $response_text = null;
|
||||
|
||||
// Did the discovery fail miserably?
|
||||
var $failed = false;
|
||||
|
||||
function Auth_Yadis_DiscoveryResult($request_uri)
|
||||
{
|
||||
// Initialize the state of the object
|
||||
// sets all attributes to None except the request_uri
|
||||
$this->request_uri = $request_uri;
|
||||
}
|
||||
|
||||
function fail()
|
||||
{
|
||||
$this->failed = true;
|
||||
}
|
||||
|
||||
function isFailure()
|
||||
{
|
||||
return $this->failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of service objects as described by the XRDS
|
||||
* document, if this yadis object represents a successful Yadis
|
||||
* discovery.
|
||||
*
|
||||
* @return array $services An array of {@link Auth_Yadis_Service}
|
||||
* objects
|
||||
*/
|
||||
function services()
|
||||
{
|
||||
if ($this->xrds) {
|
||||
return $this->xrds->services();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function usedYadisLocation()
|
||||
{
|
||||
// Was the Yadis protocol's indirection used?
|
||||
return $this->normalized_uri != $this->xrds_uri;
|
||||
}
|
||||
|
||||
function isXRDS()
|
||||
{
|
||||
// Is the response text supposed to be an XRDS document?
|
||||
return ($this->usedYadisLocation() ||
|
||||
$this->content_type == Auth_Yadis_CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Perform the Yadis protocol on the input URL and return an iterable
|
||||
* of resulting endpoint objects.
|
||||
*
|
||||
* input_url: The URL on which to perform the Yadis protocol
|
||||
*
|
||||
* @return: The normalized identity URL and an iterable of endpoint
|
||||
* objects generated by the filter function.
|
||||
*
|
||||
* xrds_parse_func: a callback which will take (uri, xrds_text) and
|
||||
* return an array of service endpoint objects or null. Usually
|
||||
* array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
|
||||
*
|
||||
* discover_func: if not null, a callback which should take (uri) and
|
||||
* return an Auth_Yadis_Yadis object or null.
|
||||
*/
|
||||
function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
|
||||
$discover_func=null, $fetcher=null)
|
||||
{
|
||||
if ($discover_func === null) {
|
||||
$discover_function = array('Auth_Yadis_Yadis', 'discover');
|
||||
}
|
||||
|
||||
$yadis_result = call_user_func_array($discover_func,
|
||||
array($input_url, $fetcher));
|
||||
|
||||
if ($yadis_result === null) {
|
||||
return array($input_url, array());
|
||||
}
|
||||
|
||||
$endpoints = call_user_func_array($xrds_parse_func,
|
||||
array($yadis_result->normalized_uri,
|
||||
$yadis_result->response_text));
|
||||
|
||||
if ($endpoints === null) {
|
||||
$endpoints = array();
|
||||
}
|
||||
|
||||
return array($yadis_result->normalized_uri, $endpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the core of the PHP Yadis library. This is the only class
|
||||
* a user needs to use to perform Yadis discovery. This class
|
||||
* performs the discovery AND stores the result of the discovery.
|
||||
*
|
||||
* First, require this library into your program source:
|
||||
*
|
||||
* <pre> require_once "Auth/Yadis/Yadis.php";</pre>
|
||||
*
|
||||
* To perform Yadis discovery, first call the "discover" method
|
||||
* statically with a URI parameter:
|
||||
*
|
||||
* <pre> $http_response = array();
|
||||
* $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
|
||||
* $yadis_object = Auth_Yadis_Yadis::discover($uri,
|
||||
* $http_response, $fetcher);</pre>
|
||||
*
|
||||
* If the discovery succeeds, $yadis_object will be an instance of
|
||||
* {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS
|
||||
* document found during discovery should have service descriptions,
|
||||
* which can be accessed by calling
|
||||
*
|
||||
* <pre> $service_list = $yadis_object->services();</pre>
|
||||
*
|
||||
* which returns an array of objects which describe each service.
|
||||
* These objects are instances of Auth_Yadis_Service. Each object
|
||||
* describes exactly one whole Service element, complete with all of
|
||||
* its Types and URIs (no expansion is performed). The common use
|
||||
* case for using the service objects returned by services() is to
|
||||
* write one or more filter functions and pass those to services():
|
||||
*
|
||||
* <pre> $service_list = $yadis_object->services(
|
||||
* array("filterByURI",
|
||||
* "filterByExtension"));</pre>
|
||||
*
|
||||
* The filter functions (whose names appear in the array passed to
|
||||
* services()) take the following form:
|
||||
*
|
||||
* <pre> function myFilter(&$service) {
|
||||
* // Query $service object here. Return true if the service
|
||||
* // matches your query; false if not.
|
||||
* }</pre>
|
||||
*
|
||||
* This is an example of a filter which uses a regular expression to
|
||||
* match the content of URI tags (note that the Auth_Yadis_Service
|
||||
* class provides a getURIs() method which you should use instead of
|
||||
* this contrived example):
|
||||
*
|
||||
* <pre>
|
||||
* function URIMatcher(&$service) {
|
||||
* foreach ($service->getElements('xrd:URI') as $uri) {
|
||||
* if (preg_match("/some_pattern/",
|
||||
* $service->parser->content($uri))) {
|
||||
* return true;
|
||||
* }
|
||||
* }
|
||||
* return false;
|
||||
* }</pre>
|
||||
*
|
||||
* The filter functions you pass will be called for each service
|
||||
* object to determine which ones match the criteria your filters
|
||||
* specify. The default behavior is that if a given service object
|
||||
* matches ANY of the filters specified in the services() call, it
|
||||
* will be returned. You can specify that a given service object will
|
||||
* be returned ONLY if it matches ALL specified filters by changing
|
||||
* the match mode of services():
|
||||
*
|
||||
* <pre> $yadis_object->services(array("filter1", "filter2"),
|
||||
* SERVICES_YADIS_MATCH_ALL);</pre>
|
||||
*
|
||||
* See {@link SERVICES_YADIS_MATCH_ALL} and {@link
|
||||
* SERVICES_YADIS_MATCH_ANY}.
|
||||
*
|
||||
* Services described in an XRDS should have a library which you'll
|
||||
* probably be using. Those libraries are responsible for defining
|
||||
* filters that can be used with the "services()" call. If you need
|
||||
* to write your own filter, see the documentation for {@link
|
||||
* Auth_Yadis_Service}.
|
||||
*
|
||||
* @package OpenID
|
||||
*/
|
||||
class Auth_Yadis_Yadis {
|
||||
|
||||
/**
|
||||
* Returns an HTTP fetcher object. If the CURL extension is
|
||||
* present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
|
||||
* is returned. If not, an instance of
|
||||
* {@link Auth_Yadis_PlainHTTPFetcher} is returned.
|
||||
*
|
||||
* If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
|
||||
* return a {@link Auth_Yadis_PlainHTTPFetcher}.
|
||||
*/
|
||||
function getHTTPFetcher($timeout = 20)
|
||||
{
|
||||
if (Auth_Yadis_Yadis::curlPresent() &&
|
||||
(!defined('Auth_Yadis_CURL_OVERRIDE'))) {
|
||||
$fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
|
||||
} else {
|
||||
$fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
|
||||
}
|
||||
return $fetcher;
|
||||
}
|
||||
|
||||
function curlPresent()
|
||||
{
|
||||
return function_exists('curl_init');
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _getHeader($header_list, $names)
|
||||
{
|
||||
foreach ($header_list as $name => $value) {
|
||||
foreach ($names as $n) {
|
||||
if (strtolower($name) == strtolower($n)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
function _getContentType($content_type_header)
|
||||
{
|
||||
if ($content_type_header) {
|
||||
$parts = explode(";", $content_type_header);
|
||||
return strtolower($parts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called statically and will build a Yadis
|
||||
* instance if the discovery process succeeds. This implements
|
||||
* Yadis discovery as specified in the Yadis specification.
|
||||
*
|
||||
* @param string $uri The URI on which to perform Yadis discovery.
|
||||
*
|
||||
* @param array $http_response An array reference where the HTTP
|
||||
* response object will be stored (see {@link
|
||||
* Auth_Yadis_HTTPResponse}.
|
||||
*
|
||||
* @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
|
||||
* Auth_Yadis_HTTPFetcher subclass.
|
||||
*
|
||||
* @param array $extra_ns_map An array which maps namespace names
|
||||
* to namespace URIs to be used when parsing the Yadis XRDS
|
||||
* document.
|
||||
*
|
||||
* @param integer $timeout An optional fetcher timeout, in seconds.
|
||||
*
|
||||
* @return mixed $obj Either null or an instance of
|
||||
* Auth_Yadis_Yadis, depending on whether the discovery
|
||||
* succeeded.
|
||||
*/
|
||||
function discover($uri, &$fetcher,
|
||||
$extra_ns_map = null, $timeout = 20)
|
||||
{
|
||||
$result = new Auth_Yadis_DiscoveryResult($uri);
|
||||
|
||||
$request_uri = $uri;
|
||||
$headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
|
||||
', text/html; q=0.3, application/xhtml+xml; q=0.5');
|
||||
|
||||
if ($fetcher === null) {
|
||||
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
|
||||
}
|
||||
|
||||
$response = $fetcher->get($uri, $headers);
|
||||
|
||||
if (!$response || ($response->status != 200 and
|
||||
$response->status != 206)) {
|
||||
$result->fail();
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result->normalized_uri = $response->final_url;
|
||||
$result->content_type = Auth_Yadis_Yadis::_getHeader(
|
||||
$response->headers,
|
||||
array('content-type'));
|
||||
|
||||
if ($result->content_type &&
|
||||
(Auth_Yadis_Yadis::_getContentType($result->content_type) ==
|
||||
Auth_Yadis_CONTENT_TYPE)) {
|
||||
$result->xrds_uri = $result->normalized_uri;
|
||||
} else {
|
||||
$yadis_location = Auth_Yadis_Yadis::_getHeader(
|
||||
$response->headers,
|
||||
array(Auth_Yadis_HEADER_NAME));
|
||||
|
||||
if (!$yadis_location) {
|
||||
$parser = new Auth_Yadis_ParseHTML();
|
||||
$yadis_location = $parser->getHTTPEquiv($response->body);
|
||||
}
|
||||
|
||||
if ($yadis_location) {
|
||||
$result->xrds_uri = $yadis_location;
|
||||
|
||||
$response = $fetcher->get($yadis_location);
|
||||
|
||||
if ((!$response) || ($response->status != 200 and
|
||||
$response->status != 206)) {
|
||||
$result->fail();
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result->content_type = Auth_Yadis_Yadis::_getHeader(
|
||||
$response->headers,
|
||||
array('content-type'));
|
||||
}
|
||||
}
|
||||
|
||||
$result->response_text = $response->body;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
546
extlib/DB/DataObject/Cast.php
Normal file
546
extlib/DB/DataObject/Cast.php
Normal file
@ -0,0 +1,546 @@
|
||||
<?php
|
||||
/**
|
||||
* Prototype Castable Object.. for DataObject queries
|
||||
*
|
||||
* Storage for Data that may be cast into a variety of formats.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB_DataObject
|
||||
* @author Alan Knowles <alan@akbkhome.com>
|
||||
* @copyright 1997-2006 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: Cast.php,v 1.15 2005/07/07 05:30:53 alan_k Exp $
|
||||
* @link http://pear.php.net/package/DB_DataObject
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Common usages:
|
||||
* // blobs
|
||||
* $data = DB_DataObject_Cast::blob($somefile);
|
||||
* $data = DB_DataObject_Cast::string($somefile);
|
||||
* $dataObject->someblobfield = $data
|
||||
*
|
||||
* // dates?
|
||||
* $d1 = new DB_DataObject_Cast::date('12/12/2000');
|
||||
* $d2 = new DB_DataObject_Cast::date(2000,12,30);
|
||||
* $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30);
|
||||
*
|
||||
* // time, datetime.. ?????????
|
||||
*
|
||||
* // raw sql????
|
||||
* $data = DB_DataObject_Cast::sql('cast("123123",datetime)');
|
||||
* $data = DB_DataObject_Cast::sql('NULL');
|
||||
*
|
||||
* // int's/string etc. are proably pretty pointless..!!!!
|
||||
*
|
||||
*
|
||||
* inside DB_DataObject,
|
||||
* if (is_a($v,'db_dataobject_class')) {
|
||||
* $value .= $v->toString(DB_DATAOBJECT_INT,'mysql');
|
||||
* }
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
|
||||
*/
|
||||
class DB_DataObject_Cast {
|
||||
|
||||
/**
|
||||
* Type of data Stored in the object..
|
||||
*
|
||||
* @var string (date|blob|.....?)
|
||||
* @access public
|
||||
*/
|
||||
var $type;
|
||||
|
||||
/**
|
||||
* Data For date representation
|
||||
*
|
||||
* @var int day/month/year
|
||||
* @access public
|
||||
*/
|
||||
var $day;
|
||||
var $month;
|
||||
var $year;
|
||||
|
||||
|
||||
/**
|
||||
* Generic Data..
|
||||
*
|
||||
* @var string
|
||||
* @access public
|
||||
*/
|
||||
|
||||
var $value;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Blob consructor
|
||||
*
|
||||
* create a Cast object from some raw data.. (binary)
|
||||
*
|
||||
*
|
||||
* @param string (with binary data!)
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
*/
|
||||
|
||||
function blob($value) {
|
||||
$r = new DB_DataObject_Cast;
|
||||
$r->type = 'blob';
|
||||
$r->value = $value;
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* String consructor (actually use if for ints and everything else!!!
|
||||
*
|
||||
* create a Cast object from some string (not binary)
|
||||
*
|
||||
*
|
||||
* @param string (with binary data!)
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
*/
|
||||
|
||||
function string($value) {
|
||||
$r = new DB_DataObject_Cast;
|
||||
$r->type = 'string';
|
||||
$r->value = $value;
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL constructor (for raw SQL insert)
|
||||
*
|
||||
* create a Cast object from some sql
|
||||
*
|
||||
* @param string (with binary data!)
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
*/
|
||||
|
||||
function sql($value)
|
||||
{
|
||||
$r = new DB_DataObject_Cast;
|
||||
$r->type = 'sql';
|
||||
$r->value = $value;
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Date Constructor
|
||||
*
|
||||
* create a Cast object from some string (not binary)
|
||||
* NO VALIDATION DONE, although some crappy re-calcing done!
|
||||
*
|
||||
* @param vargs... accepts
|
||||
* dd/mm
|
||||
* dd/mm/yyyy
|
||||
* yyyy-mm
|
||||
* yyyy-mm-dd
|
||||
* array(yyyy,dd)
|
||||
* array(yyyy,dd,mm)
|
||||
*
|
||||
*
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
*/
|
||||
|
||||
function date()
|
||||
{
|
||||
$args = func_get_args();
|
||||
switch(count($args)) {
|
||||
case 0: // no args = today!
|
||||
$bits = explode('-',date('Y-m-d'));
|
||||
break;
|
||||
case 1: // one arg = a string
|
||||
|
||||
if (strpos($args[0],'/') !== false) {
|
||||
$bits = array_reverse(explode('/',$args[0]));
|
||||
} else {
|
||||
$bits = explode('-',$args[0]);
|
||||
}
|
||||
break;
|
||||
default: // 2 or more..
|
||||
$bits = $args;
|
||||
}
|
||||
if (count($bits) == 1) { // if YYYY set day = 1st..
|
||||
$bits[] = 1;
|
||||
}
|
||||
|
||||
if (count($bits) == 2) { // if YYYY-DD set day = 1st..
|
||||
$bits[] = 1;
|
||||
}
|
||||
|
||||
// if year < 1970 we cant use system tools to check it...
|
||||
// so we make a few best gueses....
|
||||
// basically do date calculations for the year 2000!!!
|
||||
// fix me if anyone has more time...
|
||||
if (($bits[0] < 1975) || ($bits[0] > 2030)) {
|
||||
$oldyear = $bits[0];
|
||||
$bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],2000)));
|
||||
$bits[0] = ($bits[0] - 2000) + $oldyear;
|
||||
} else {
|
||||
// now mktime
|
||||
$bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],$bits[0])));
|
||||
}
|
||||
$r = new DB_DataObject_Cast;
|
||||
$r->type = 'date';
|
||||
list($r->year,$r->month,$r->day) = $bits;
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Data For time representation ** does not handle timezones!!
|
||||
*
|
||||
* @var int hour/minute/second
|
||||
* @access public
|
||||
*/
|
||||
var $hour;
|
||||
var $minute;
|
||||
var $second;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime Constructor
|
||||
*
|
||||
* create a Cast object from a Date/Time
|
||||
* Maybe should accept a Date object.!
|
||||
* NO VALIDATION DONE, although some crappy re-calcing done!
|
||||
*
|
||||
* @param vargs... accepts
|
||||
* noargs (now)
|
||||
* yyyy-mm-dd HH:MM:SS (Iso)
|
||||
* array(yyyy,mm,dd,HH,MM,SS)
|
||||
*
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
* @author therion 5 at hotmail
|
||||
*/
|
||||
|
||||
function dateTime()
|
||||
{
|
||||
$args = func_get_args();
|
||||
switch(count($args)) {
|
||||
case 0: // no args = now!
|
||||
$datetime = date('Y-m-d G:i:s', mktime());
|
||||
|
||||
case 1:
|
||||
// continue on from 0 args.
|
||||
if (!isset($datetime)) {
|
||||
$datetime = $args[0];
|
||||
}
|
||||
|
||||
$parts = explode(' ', $datetime);
|
||||
$bits = explode('-', $parts[0]);
|
||||
$bits = array_merge($bits, explode(':', $parts[1]));
|
||||
break;
|
||||
|
||||
default: // 2 or more..
|
||||
$bits = $args;
|
||||
|
||||
}
|
||||
|
||||
if (count($bits) != 6) {
|
||||
// PEAR ERROR?
|
||||
return false;
|
||||
}
|
||||
|
||||
$r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]);
|
||||
if (!$r) {
|
||||
return $r; // pass thru error (False) - doesnt happen at present!
|
||||
}
|
||||
// change the type!
|
||||
$r->type = 'datetime';
|
||||
|
||||
// should we mathematically sort this out..
|
||||
// (or just assume that no-one's dumb enough to enter 26:90:90 as a time!
|
||||
$r->hour = $bits[3];
|
||||
$r->minute = $bits[4];
|
||||
$r->second = $bits[5];
|
||||
return $r;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* time Constructor
|
||||
*
|
||||
* create a Cast object from a Date/Time
|
||||
* Maybe should accept a Date object.!
|
||||
* NO VALIDATION DONE, and no-recalcing done!
|
||||
*
|
||||
* @param vargs... accepts
|
||||
* noargs (now)
|
||||
* HH:MM:SS (Iso)
|
||||
* array(HH,MM,SS)
|
||||
*
|
||||
*
|
||||
* @return object DB_DataObject_Cast
|
||||
* @access public
|
||||
* @author therion 5 at hotmail
|
||||
*/
|
||||
function time()
|
||||
{
|
||||
$args = func_get_args();
|
||||
switch (count($args)) {
|
||||
case 0: // no args = now!
|
||||
$time = date('G:i:s', mktime());
|
||||
|
||||
case 1:
|
||||
// continue on from 0 args.
|
||||
if (!isset($time)) {
|
||||
$time = $args[0];
|
||||
}
|
||||
$bits = explode(':', $time);
|
||||
break;
|
||||
|
||||
default: // 2 or more..
|
||||
$bits = $args;
|
||||
|
||||
}
|
||||
|
||||
if (count($bits) != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// now take data from bits into object fields
|
||||
$r = new DB_DataObject_Cast;
|
||||
$r->type = 'time';
|
||||
$r->hour = $bits[0];
|
||||
$r->minute = $bits[1];
|
||||
$r->second = $bits[2];
|
||||
return $r;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for this...
|
||||
*
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
*/
|
||||
|
||||
function toString($to=false,$db)
|
||||
{
|
||||
// if $this->type is not set, we are in serious trouble!!!!
|
||||
// values for to:
|
||||
$method = 'toStringFrom'.$this->type;
|
||||
return $this->$method($to,$db);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement from a blob of binary data
|
||||
* ** Suppots only blob->postgres::bytea
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
*/
|
||||
function toStringFromBlob($to,$db)
|
||||
{
|
||||
// first weed out invalid casts..
|
||||
// in blobs can only be cast to blobs.!
|
||||
|
||||
// perhaps we should support TEXT fields???
|
||||
|
||||
if (!($to & DB_DATAOBJECT_BLOB)) {
|
||||
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!');
|
||||
}
|
||||
|
||||
switch ($db->dsn["phptype"]) {
|
||||
case 'pgsql':
|
||||
return "'".pg_escape_bytea($this->value)."'::bytea";
|
||||
|
||||
case 'mysql':
|
||||
return "'".mysql_real_escape_string($this->value,$db->connection)."'";
|
||||
|
||||
case 'mysqli':
|
||||
// this is funny - the parameter order is reversed ;)
|
||||
return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
|
||||
|
||||
|
||||
|
||||
default:
|
||||
return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for a blob from a string!
|
||||
* ** Suppots only string->postgres::bytea
|
||||
*
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
*/
|
||||
function toStringFromString($to,$db)
|
||||
{
|
||||
// first weed out invalid casts..
|
||||
// in blobs can only be cast to blobs.!
|
||||
|
||||
// perhaps we should support TEXT fields???
|
||||
//
|
||||
|
||||
if (!($to & DB_DATAOBJECT_BLOB)) {
|
||||
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a blob!'.
|
||||
' (why not just use native features)');
|
||||
}
|
||||
|
||||
switch ($db->dsn['phptype']) {
|
||||
case 'pgsql':
|
||||
return "'".pg_escape_string($this->value)."'::bytea";
|
||||
|
||||
case 'mysql':
|
||||
return "'".mysql_real_escape_string($this->value,$db->connection)."'";
|
||||
|
||||
|
||||
case 'mysqli':
|
||||
return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
|
||||
|
||||
|
||||
default:
|
||||
return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for a date
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
*/
|
||||
function toStringFromDate($to,$db)
|
||||
{
|
||||
// first weed out invalid casts..
|
||||
// in blobs can only be cast to blobs.!
|
||||
// perhaps we should support TEXT fields???
|
||||
//
|
||||
|
||||
if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) {
|
||||
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'.
|
||||
' (why not just use native features)');
|
||||
}
|
||||
return "'{$this->year}-{$this->month}-{$this->day}'";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for a datetime
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
* @author therion 5 at hotmail
|
||||
*/
|
||||
|
||||
function toStringFromDateTime($to,$db)
|
||||
{
|
||||
// first weed out invalid casts..
|
||||
// in blobs can only be cast to blobs.!
|
||||
// perhaps we should support TEXT fields???
|
||||
if (($to !== false) &&
|
||||
!($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) {
|
||||
return PEAR::raiseError('Invalid Cast from a ' .
|
||||
' DB_DataObject_Cast::dateTime to something other than a datetime!' .
|
||||
' (try using native features)');
|
||||
}
|
||||
return "'{$this->year}-{$this->month}-{$this->day} {$this->hour}:{$this->minute}:{$this->second}'";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for a time
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
* @author therion 5 at hotmail
|
||||
*/
|
||||
|
||||
function toStringFromTime($to,$db)
|
||||
{
|
||||
// first weed out invalid casts..
|
||||
// in blobs can only be cast to blobs.!
|
||||
// perhaps we should support TEXT fields???
|
||||
if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) {
|
||||
return PEAR::raiseError('Invalid Cast from a' .
|
||||
' DB_DataObject_Cast::time to something other than a time!'.
|
||||
' (try using native features)');
|
||||
}
|
||||
return "'{$this->hour}:{$this->minute}:{$this->second}'";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the string to use in the SQL statement for a raw sql statement.
|
||||
*
|
||||
* @param int $to Type (DB_DATAOBJECT_*
|
||||
* @param object $db DB Connection Object
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @access public
|
||||
*/
|
||||
function toStringFromSql($to,$db)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
53
extlib/DB/DataObject/Error.php
Normal file
53
extlib/DB/DataObject/Error.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* DataObjects error handler, loaded on demand...
|
||||
*
|
||||
* DB_DataObject_Error is a quick wrapper around pear error, so you can distinguish the
|
||||
* error code source.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB_DataObject
|
||||
* @author Alan Knowles <alan@akbkhome.com>
|
||||
* @copyright 1997-2006 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: Error.php,v 1.3 2005/03/23 02:35:35 alan_k Exp $
|
||||
* @link http://pear.php.net/package/DB_DataObject
|
||||
*/
|
||||
|
||||
|
||||
class DB_DataObject_Error extends PEAR_Error
|
||||
{
|
||||
|
||||
/**
|
||||
* DB_DataObject_Error constructor.
|
||||
*
|
||||
* @param mixed $code DB error code, or string with error message.
|
||||
* @param integer $mode what "error mode" to operate in
|
||||
* @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER
|
||||
* @param mixed $debuginfo additional debug info, such as the last query
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @see PEAR_Error
|
||||
*/
|
||||
function DB_DataObject_Error($message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
|
||||
$level = E_USER_NOTICE)
|
||||
{
|
||||
$this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// todo : - support code -> message handling, and translated error messages...
|
||||
|
||||
|
||||
|
||||
}
|
1553
extlib/DB/DataObject/Generator.php
Normal file
1553
extlib/DB/DataObject/Generator.php
Normal file
File diff suppressed because it is too large
Load Diff
59
extlib/DB/DataObject/createTables.php
Executable file
59
extlib/DB/DataObject/createTables.php
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/php -q
|
||||
<?php
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Author: Alan Knowles <alan@akbkhome.com>
|
||||
// +----------------------------------------------------------------------+
|
||||
//
|
||||
// $Id: createTables.php,v 1.24 2006/01/13 01:27:55 alan_k Exp $
|
||||
//
|
||||
|
||||
// since this version doesnt use overload,
|
||||
// and I assume anyone using custom generators should add this..
|
||||
|
||||
define('DB_DATAOBJECT_NO_OVERLOAD',1);
|
||||
|
||||
//require_once 'DB/DataObject/Generator.php';
|
||||
require_once 'DB/DataObject/Generator.php';
|
||||
|
||||
if (!ini_get('register_argc_argv')) {
|
||||
PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!@$_SERVER['argv'][1]) {
|
||||
PEAR::raiseError("\nERROR: createTable.php usage:\n\nC:\php\pear\DB\DataObjects\createTable.php example.ini\n\n", null, PEAR_ERROR_DIE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$config = parse_ini_file($_SERVER['argv'][1], true);
|
||||
foreach($config as $class=>$values) {
|
||||
$options = &PEAR::getStaticProperty($class,'options');
|
||||
$options = $values;
|
||||
}
|
||||
|
||||
|
||||
$options = &PEAR::getStaticProperty('DB_DataObject','options');
|
||||
if (empty($options)) {
|
||||
PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE);
|
||||
exit;
|
||||
}
|
||||
set_time_limit(0);
|
||||
|
||||
// use debug level from file if set..
|
||||
DB_DataObject::debugLevel(isset($options['debug']) ? $options['debug'] : 1);
|
||||
|
||||
$generator = new DB_DataObject_Generator;
|
||||
$generator->start();
|
||||
|
2262
extlib/DB/common.php
Normal file
2262
extlib/DB/common.php
Normal file
File diff suppressed because it is too large
Load Diff
510
extlib/DB/dbase.php
Normal file
510
extlib/DB/dbase.php
Normal file
@ -0,0 +1,510 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's dbase extension
|
||||
* for interacting with dBase databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Tomas V.V. Cox <cox@idecnet.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: dbase.php,v 1.45 2007/09/21 13:40:41 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's dbase extension
|
||||
* for interacting with dBase databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Tomas V.V. Cox <cox@idecnet.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_dbase extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'dbase';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'dbase';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => false,
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => false,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* A means of emulating result resources
|
||||
* @var array
|
||||
*/
|
||||
var $res_row = array();
|
||||
|
||||
/**
|
||||
* The quantity of results so far
|
||||
*
|
||||
* For emulating result resources.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
var $result = 0;
|
||||
|
||||
/**
|
||||
* Maps dbase data type id's to human readable strings
|
||||
*
|
||||
* The human readable values are based on the output of PHP's
|
||||
* dbase_get_header_info() function.
|
||||
*
|
||||
* @var array
|
||||
* @since Property available since Release 1.7.0
|
||||
*/
|
||||
var $types = array(
|
||||
'C' => 'character',
|
||||
'D' => 'date',
|
||||
'L' => 'boolean',
|
||||
'M' => 'memo',
|
||||
'N' => 'number',
|
||||
);
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_dbase()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database and create it if it doesn't exist
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* PEAR DB's dbase driver supports the following extra DSN options:
|
||||
* + mode An integer specifying the read/write mode to use
|
||||
* (0 = read only, 1 = write only, 2 = read/write).
|
||||
* Available since PEAR DB 1.7.0.
|
||||
* + fields An array of arrays that PHP's dbase_create() function needs
|
||||
* to create a new database. This information is used if the
|
||||
* dBase file specified in the "database" segment of the DSN
|
||||
* does not exist. For more info, see the PHP manual's
|
||||
* {@link http://php.net/dbase_create dbase_create()} page.
|
||||
* Available since PEAR DB 1.7.0.
|
||||
*
|
||||
* Example of how to connect and establish a new dBase file if necessary:
|
||||
* <code>
|
||||
* require_once 'DB.php';
|
||||
*
|
||||
* $dsn = array(
|
||||
* 'phptype' => 'dbase',
|
||||
* 'database' => '/path/and/name/of/dbase/file',
|
||||
* 'mode' => 2,
|
||||
* 'fields' => array(
|
||||
* array('a', 'N', 5, 0),
|
||||
* array('b', 'C', 40),
|
||||
* array('c', 'C', 255),
|
||||
* array('d', 'C', 20),
|
||||
* ),
|
||||
* );
|
||||
* $options = array(
|
||||
* 'debug' => 2,
|
||||
* 'portability' => DB_PORTABILITY_ALL,
|
||||
* );
|
||||
*
|
||||
* $db = DB::connect($dsn, $options);
|
||||
* if (PEAR::isError($db)) {
|
||||
* die($db->getMessage());
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('dbase')) {
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn track_errors on for entire script since $php_errormsg
|
||||
* is the only way to find errors from the dbase extension.
|
||||
*/
|
||||
@ini_set('track_errors', 1);
|
||||
$php_errormsg = '';
|
||||
|
||||
if (!file_exists($dsn['database'])) {
|
||||
$this->dsn['mode'] = 2;
|
||||
if (empty($dsn['fields']) || !is_array($dsn['fields'])) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
'the dbase file does not exist and '
|
||||
. 'it could not be created because '
|
||||
. 'the "fields" element of the DSN '
|
||||
. 'is not properly set');
|
||||
}
|
||||
$this->connection = @dbase_create($dsn['database'],
|
||||
$dsn['fields']);
|
||||
if (!$this->connection) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
'the dbase file does not exist and '
|
||||
. 'the attempt to create it failed: '
|
||||
. $php_errormsg);
|
||||
}
|
||||
} else {
|
||||
if (!isset($this->dsn['mode'])) {
|
||||
$this->dsn['mode'] = 0;
|
||||
}
|
||||
$this->connection = @dbase_open($dsn['database'],
|
||||
$this->dsn['mode']);
|
||||
if (!$this->connection) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @dbase_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ &query()
|
||||
|
||||
function &query($query = null)
|
||||
{
|
||||
// emulate result resources
|
||||
$this->res_row[(int)$this->result] = 0;
|
||||
$tmp = new DB_result($this, $this->result++);
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum === null) {
|
||||
$rownum = $this->res_row[(int)$result]++;
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
$arr = @dbase_get_record_with_names($this->connection, $rownum);
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
$arr = @dbase_get_record($this->connection, $rownum);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set.
|
||||
*
|
||||
* This method is a no-op for dbase, as there aren't result resources in
|
||||
* the same sense as most other database backends.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($foo)
|
||||
{
|
||||
return @dbase_numfields($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($foo)
|
||||
{
|
||||
return @dbase_numrecords($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteBoolean()
|
||||
|
||||
/**
|
||||
* Formats a boolean value for use within a query in a locale-independent
|
||||
* manner.
|
||||
*
|
||||
* @param boolean the boolean value to be quoted.
|
||||
* @return string the quoted string.
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since release 1.7.8.
|
||||
*/
|
||||
function quoteBoolean($boolean) {
|
||||
return $boolean ? 'T' : 'F';
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about the current database
|
||||
*
|
||||
* @param mixed $result THIS IS UNUSED IN DBASE. The current database
|
||||
* is examined regardless of what is provided here.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function tableInfo($result = null, $mode = null)
|
||||
{
|
||||
if (function_exists('dbase_get_header_info')) {
|
||||
$id = @dbase_get_header_info($this->connection);
|
||||
if (!$id && $php_errormsg) {
|
||||
return $this->raiseError(DB_ERROR,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* This segment for PHP 4 is loosely based on code by
|
||||
* Hadi Rusiah <deegos@yahoo.com> in the comments on
|
||||
* the dBase reference page in the PHP manual.
|
||||
*/
|
||||
$db = @fopen($this->dsn['database'], 'r');
|
||||
if (!$db) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
|
||||
$id = array();
|
||||
$i = 0;
|
||||
|
||||
$line = fread($db, 32);
|
||||
while (!feof($db)) {
|
||||
$line = fread($db, 32);
|
||||
if (substr($line, 0, 1) == chr(13)) {
|
||||
break;
|
||||
} else {
|
||||
$pos = strpos(substr($line, 0, 10), chr(0));
|
||||
$pos = ($pos == 0 ? 10 : $pos);
|
||||
$id[$i] = array(
|
||||
'name' => substr($line, 0, $pos),
|
||||
'type' => $this->types[substr($line, 11, 1)],
|
||||
'length' => ord(substr($line, 16, 1)),
|
||||
'precision' => ord(substr($line, 17, 1)),
|
||||
);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
fclose($db);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$res = array();
|
||||
$count = count($id);
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$res[$i] = array(
|
||||
'table' => $this->dsn['database'],
|
||||
'name' => $case_func($id[$i]['name']),
|
||||
'type' => $id[$i]['type'],
|
||||
'len' => $id[$i]['length'],
|
||||
'flags' => ''
|
||||
);
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
769
extlib/DB/fbsql.php
Normal file
769
extlib/DB/fbsql.php
Normal file
@ -0,0 +1,769 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's fbsql extension
|
||||
* for interacting with FrontBase databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Frank M. Kromann <frank@frontbase.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: fbsql.php,v 1.88 2007/07/06 05:19:21 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's fbsql extension
|
||||
* for interacting with FrontBase databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Frank M. Kromann <frank@frontbase.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
* @since Class functional since Release 1.7.0
|
||||
*/
|
||||
class DB_fbsql extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'fbsql';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'fbsql';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'alter',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
22 => DB_ERROR_SYNTAX,
|
||||
85 => DB_ERROR_ALREADY_EXISTS,
|
||||
108 => DB_ERROR_SYNTAX,
|
||||
116 => DB_ERROR_NOSUCHTABLE,
|
||||
124 => DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
215 => DB_ERROR_NOSUCHFIELD,
|
||||
217 => DB_ERROR_INVALID_NUMBER,
|
||||
226 => DB_ERROR_NOSUCHFIELD,
|
||||
231 => DB_ERROR_INVALID,
|
||||
239 => DB_ERROR_TRUNCATED,
|
||||
251 => DB_ERROR_SYNTAX,
|
||||
266 => DB_ERROR_NOT_FOUND,
|
||||
357 => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
358 => DB_ERROR_CONSTRAINT,
|
||||
360 => DB_ERROR_CONSTRAINT,
|
||||
361 => DB_ERROR_CONSTRAINT,
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_fbsql()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('fbsql')) {
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
$params = array(
|
||||
$dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
|
||||
$dsn['username'] ? $dsn['username'] : null,
|
||||
$dsn['password'] ? $dsn['password'] : null,
|
||||
);
|
||||
|
||||
$connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
|
||||
|
||||
$ini = ini_get('track_errors');
|
||||
$php_errormsg = '';
|
||||
if ($ini) {
|
||||
$this->connection = @call_user_func_array($connect_function,
|
||||
$params);
|
||||
} else {
|
||||
@ini_set('track_errors', 1);
|
||||
$this->connection = @call_user_func_array($connect_function,
|
||||
$params);
|
||||
@ini_set('track_errors', $ini);
|
||||
}
|
||||
|
||||
if (!$this->connection) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
|
||||
if ($dsn['database']) {
|
||||
if (!@fbsql_select_db($dsn['database'], $this->connection)) {
|
||||
return $this->fbsqlRaiseError();
|
||||
}
|
||||
}
|
||||
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @fbsql_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$this->last_query = $query;
|
||||
$query = $this->modifyQuery($query);
|
||||
$result = @fbsql_query("$query;", $this->connection);
|
||||
if (!$result) {
|
||||
return $this->fbsqlRaiseError();
|
||||
}
|
||||
// Determine which queries that should return data, and which
|
||||
// should return an error code only.
|
||||
if ($this->_checkManip($query)) {
|
||||
return DB_OK;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal fbsql result pointer to the next available result
|
||||
*
|
||||
* @param a valid fbsql result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return @fbsql_next_result($result);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum !== null) {
|
||||
if (!@fbsql_data_seek($result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
$arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
$arr = @fbsql_fetch_row($result);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? fbsql_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ autoCommit()
|
||||
|
||||
/**
|
||||
* Enables or disables automatic commits
|
||||
*
|
||||
* @param bool $onoff true turns it on, false turns it off
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object if the driver
|
||||
* doesn't support auto-committing transactions.
|
||||
*/
|
||||
function autoCommit($onoff=false)
|
||||
{
|
||||
if ($onoff) {
|
||||
$this->query("SET COMMIT TRUE");
|
||||
} else {
|
||||
$this->query("SET COMMIT FALSE");
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ commit()
|
||||
|
||||
/**
|
||||
* Commits the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function commit()
|
||||
{
|
||||
@fbsql_commit($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ rollback()
|
||||
|
||||
/**
|
||||
* Reverts the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function rollback()
|
||||
{
|
||||
@fbsql_rollback($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @fbsql_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->fbsqlRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$rows = @fbsql_num_rows($result);
|
||||
if ($rows === null) {
|
||||
return $this->fbsqlRaiseError();
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affectedRows()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if ($this->_last_query_manip) {
|
||||
$result = @fbsql_affected_rows($this->connection);
|
||||
} else {
|
||||
$result = 0;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_fbsql::createSequence(), DB_fbsql::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
do {
|
||||
$repeat = 0;
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query('SELECT UNIQUE FROM ' . $seqname);
|
||||
$this->popErrorHandling();
|
||||
if ($ondemand && DB::isError($result) &&
|
||||
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
|
||||
$repeat = 1;
|
||||
$result = $this->createSequence($seq_name);
|
||||
if (DB::isError($result)) {
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
$repeat = 0;
|
||||
}
|
||||
} while ($repeat);
|
||||
if (DB::isError($result)) {
|
||||
return $this->fbsqlRaiseError();
|
||||
}
|
||||
$result->fetchInto($tmp, DB_FETCHMODE_ORDERED);
|
||||
return $tmp[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_fbsql::nextID(), DB_fbsql::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
$res = $this->query('CREATE TABLE ' . $seqname
|
||||
. ' (id INTEGER NOT NULL,'
|
||||
. ' PRIMARY KEY(id))');
|
||||
if ($res) {
|
||||
$res = $this->query('SET UNIQUE = 0 FOR ' . $seqname);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_fbsql::nextID(), DB_fbsql::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)
|
||||
. ' RESTRICT');
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ modifyLimitQuery()
|
||||
|
||||
/**
|
||||
* Adds LIMIT clauses to a query string according to current DBMS standards
|
||||
*
|
||||
* @param string $query the query to modify
|
||||
* @param int $from the row to start to fetching (0 = the first row)
|
||||
* @param int $count the numbers of rows to fetch
|
||||
* @param mixed $params array, string or numeric data to be used in
|
||||
* execution of the statement. Quantity of items
|
||||
* passed must match quantity of placeholders in
|
||||
* query: meaning 1 placeholder for non-array
|
||||
* parameters or 1 placeholder per array element.
|
||||
*
|
||||
* @return string the query string with LIMIT clauses added
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
function modifyLimitQuery($query, $from, $count, $params = array())
|
||||
{
|
||||
if (DB::isManip($query) || $this->_next_query_manip) {
|
||||
return preg_replace('/^([\s(])*SELECT/i',
|
||||
"\\1SELECT TOP($count)", $query);
|
||||
} else {
|
||||
return preg_replace('/([\s(])*SELECT/i',
|
||||
"\\1SELECT TOP($from, $count)", $query);
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteBoolean()
|
||||
|
||||
/**
|
||||
* Formats a boolean value for use within a query in a locale-independent
|
||||
* manner.
|
||||
*
|
||||
* @param boolean the boolean value to be quoted.
|
||||
* @return string the quoted string.
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since release 1.7.8.
|
||||
*/
|
||||
function quoteBoolean($boolean) {
|
||||
return $boolean ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteFloat()
|
||||
|
||||
/**
|
||||
* Formats a float value for use within a query in a locale-independent
|
||||
* manner.
|
||||
*
|
||||
* @param float the float value to be quoted.
|
||||
* @return string the quoted string.
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since release 1.7.8.
|
||||
*/
|
||||
function quoteFloat($float) {
|
||||
return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fbsqlRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_fbsql::errorNative(), DB_common::errorCode()
|
||||
*/
|
||||
function fbsqlRaiseError($errno = null)
|
||||
{
|
||||
if ($errno === null) {
|
||||
$errno = $this->errorCode(fbsql_errno($this->connection));
|
||||
}
|
||||
return $this->raiseError($errno, null, null, null,
|
||||
@fbsql_error($this->connection));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error code produced by the last query
|
||||
*
|
||||
* @return int the DBMS' error code
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
return @fbsql_errno($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
$id = @fbsql_list_fields($this->dsn['database'],
|
||||
$result, $this->connection);
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
* Deprecated. Here for compatibility only.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = @fbsql_num_fields($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$res[$i] = array(
|
||||
'table' => $case_func(@fbsql_field_table($id, $i)),
|
||||
'name' => $case_func(@fbsql_field_name($id, $i)),
|
||||
'type' => @fbsql_field_type($id, $i),
|
||||
'len' => @fbsql_field_len($id, $i),
|
||||
'flags' => @fbsql_field_flags($id, $i),
|
||||
);
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@fbsql_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return string the SQL query string or null if the driver doesn't
|
||||
* support the object type requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'tables':
|
||||
return 'SELECT "table_name" FROM information_schema.tables'
|
||||
. ' t0, information_schema.schemata t1'
|
||||
. ' WHERE t0.schema_pk=t1.schema_pk AND'
|
||||
. ' "table_type" = \'BASE TABLE\''
|
||||
. ' AND "schema_name" = current_schema';
|
||||
case 'views':
|
||||
return 'SELECT "table_name" FROM information_schema.tables'
|
||||
. ' t0, information_schema.schemata t1'
|
||||
. ' WHERE t0.schema_pk=t1.schema_pk AND'
|
||||
. ' "table_type" = \'VIEW\''
|
||||
. ' AND "schema_name" = current_schema';
|
||||
case 'users':
|
||||
return 'SELECT "user_name" from information_schema.users';
|
||||
case 'functions':
|
||||
return 'SELECT "routine_name" FROM'
|
||||
. ' information_schema.psm_routines'
|
||||
. ' t0, information_schema.schemata t1'
|
||||
. ' WHERE t0.schema_pk=t1.schema_pk'
|
||||
. ' AND "routine_kind"=\'FUNCTION\''
|
||||
. ' AND "schema_name" = current_schema';
|
||||
case 'procedures':
|
||||
return 'SELECT "routine_name" FROM'
|
||||
. ' information_schema.psm_routines'
|
||||
. ' t0, information_schema.schemata t1'
|
||||
. ' WHERE t0.schema_pk=t1.schema_pk'
|
||||
. ' AND "routine_kind"=\'PROCEDURE\''
|
||||
. ' AND "schema_name" = current_schema';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
1082
extlib/DB/ibase.php
Normal file
1082
extlib/DB/ibase.php
Normal file
File diff suppressed because it is too large
Load Diff
683
extlib/DB/ifx.php
Normal file
683
extlib/DB/ifx.php
Normal file
@ -0,0 +1,683 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's ifx extension
|
||||
* for interacting with Informix databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Tomas V.V.Cox <cox@idecnet.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: ifx.php,v 1.75 2007/07/06 05:19:21 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's ifx extension
|
||||
* for interacting with Informix databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* More info on Informix errors can be found at:
|
||||
* http://www.informix.com/answers/english/ierrors.htm
|
||||
*
|
||||
* TODO:
|
||||
* - set needed env Informix vars on connect
|
||||
* - implement native prepare/execute
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Tomas V.V.Cox <cox@idecnet.com>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_ifx extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'ifx';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'ifx';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'emulate',
|
||||
'new_link' => false,
|
||||
'numrows' => 'emulate',
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
'-201' => DB_ERROR_SYNTAX,
|
||||
'-206' => DB_ERROR_NOSUCHTABLE,
|
||||
'-217' => DB_ERROR_NOSUCHFIELD,
|
||||
'-236' => DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
'-239' => DB_ERROR_CONSTRAINT,
|
||||
'-253' => DB_ERROR_SYNTAX,
|
||||
'-268' => DB_ERROR_CONSTRAINT,
|
||||
'-292' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'-310' => DB_ERROR_ALREADY_EXISTS,
|
||||
'-316' => DB_ERROR_ALREADY_EXISTS,
|
||||
'-319' => DB_ERROR_NOT_FOUND,
|
||||
'-329' => DB_ERROR_NODBSELECTED,
|
||||
'-346' => DB_ERROR_CONSTRAINT,
|
||||
'-386' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'-391' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'-554' => DB_ERROR_SYNTAX,
|
||||
'-691' => DB_ERROR_CONSTRAINT,
|
||||
'-692' => DB_ERROR_CONSTRAINT,
|
||||
'-703' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'-1202' => DB_ERROR_DIVZERO,
|
||||
'-1204' => DB_ERROR_INVALID_DATE,
|
||||
'-1205' => DB_ERROR_INVALID_DATE,
|
||||
'-1206' => DB_ERROR_INVALID_DATE,
|
||||
'-1209' => DB_ERROR_INVALID_DATE,
|
||||
'-1210' => DB_ERROR_INVALID_DATE,
|
||||
'-1212' => DB_ERROR_INVALID_DATE,
|
||||
'-1213' => DB_ERROR_INVALID_NUMBER,
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* Should data manipulation queries be committed automatically?
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
var $autocommit = true;
|
||||
|
||||
/**
|
||||
* The quantity of transactions begun
|
||||
*
|
||||
* {@internal While this is private, it can't actually be designated
|
||||
* private in PHP 5 because it is directly accessed in the test suite.}}
|
||||
*
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
var $transaction_opcount = 0;
|
||||
|
||||
/**
|
||||
* The number of rows affected by a data manipulation query
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
var $affected = 0;
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_ifx()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('informix') &&
|
||||
!PEAR::loadExtension('Informix'))
|
||||
{
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
$dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : '';
|
||||
$dbname = $dsn['database'] ? $dsn['database'] . $dbhost : '';
|
||||
$user = $dsn['username'] ? $dsn['username'] : '';
|
||||
$pw = $dsn['password'] ? $dsn['password'] : '';
|
||||
|
||||
$connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect';
|
||||
|
||||
$this->connection = @$connect_function($dbname, $user, $pw);
|
||||
if (!is_resource($this->connection)) {
|
||||
return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @ifx_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$ismanip = $this->_checkManip($query);
|
||||
$this->last_query = $query;
|
||||
$this->affected = null;
|
||||
if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()?
|
||||
// the scroll is needed for fetching absolute row numbers
|
||||
// in a select query result
|
||||
$result = @ifx_query($query, $this->connection, IFX_SCROLL);
|
||||
} else {
|
||||
if (!$this->autocommit && $ismanip) {
|
||||
if ($this->transaction_opcount == 0) {
|
||||
$result = @ifx_query('BEGIN WORK', $this->connection);
|
||||
if (!$result) {
|
||||
return $this->ifxRaiseError();
|
||||
}
|
||||
}
|
||||
$this->transaction_opcount++;
|
||||
}
|
||||
$result = @ifx_query($query, $this->connection);
|
||||
}
|
||||
if (!$result) {
|
||||
return $this->ifxRaiseError();
|
||||
}
|
||||
$this->affected = @ifx_affected_rows($result);
|
||||
// Determine which queries should return data, and which
|
||||
// should return an error code only.
|
||||
if (preg_match('/(SELECT|EXECUTE)/i', $query)) {
|
||||
return $result;
|
||||
}
|
||||
// XXX Testme: free results inside a transaction
|
||||
// may cause to stop it and commit the work?
|
||||
|
||||
// Result has to be freed even with a insert or update
|
||||
@ifx_free_result($result);
|
||||
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal ifx result pointer to the next available result
|
||||
*
|
||||
* @param a valid fbsql result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affectedRows()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if ($this->_last_query_manip) {
|
||||
return $this->affected;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if (($rownum !== null) && ($rownum < 0)) {
|
||||
return null;
|
||||
}
|
||||
if ($rownum === null) {
|
||||
/*
|
||||
* Even though fetch_row() should return the next row if
|
||||
* $rownum is null, it doesn't in all cases. Bug 598.
|
||||
*/
|
||||
$rownum = 'NEXT';
|
||||
} else {
|
||||
// Index starts at row 1, unlike most DBMS's starting at 0.
|
||||
$rownum++;
|
||||
}
|
||||
if (!$arr = @ifx_fetch_row($result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
if ($fetchmode !== DB_FETCHMODE_ASSOC) {
|
||||
$i=0;
|
||||
$order = array();
|
||||
foreach ($arr as $val) {
|
||||
$order[$i++] = $val;
|
||||
}
|
||||
$arr = $order;
|
||||
} elseif ($fetchmode == DB_FETCHMODE_ASSOC &&
|
||||
$this->options['portability'] & DB_PORTABILITY_LOWERCASE)
|
||||
{
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
if (!$cols = @ifx_num_fields($result)) {
|
||||
return $this->ifxRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? ifx_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ autoCommit()
|
||||
|
||||
/**
|
||||
* Enables or disables automatic commits
|
||||
*
|
||||
* @param bool $onoff true turns it on, false turns it off
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object if the driver
|
||||
* doesn't support auto-committing transactions.
|
||||
*/
|
||||
function autoCommit($onoff = true)
|
||||
{
|
||||
// XXX if $this->transaction_opcount > 0, we should probably
|
||||
// issue a warning here.
|
||||
$this->autocommit = $onoff ? true : false;
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ commit()
|
||||
|
||||
/**
|
||||
* Commits the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function commit()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
$result = @ifx_query('COMMIT WORK', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->ifxRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ rollback()
|
||||
|
||||
/**
|
||||
* Reverts the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function rollback()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
$result = @ifx_query('ROLLBACK WORK', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->ifxRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ ifxRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_ifx::errorNative(), DB_ifx::errorCode()
|
||||
*/
|
||||
function ifxRaiseError($errno = null)
|
||||
{
|
||||
if ($errno === null) {
|
||||
$errno = $this->errorCode(ifx_error());
|
||||
}
|
||||
return $this->raiseError($errno, null, null, null,
|
||||
$this->errorNative());
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error code and message produced by the last query
|
||||
*
|
||||
* @return string the DBMS' error code and message
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
return @ifx_error() . ' ' . @ifx_errormsg();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorCode()
|
||||
|
||||
/**
|
||||
* Maps native error codes to DB's portable ones.
|
||||
*
|
||||
* Requires that the DB implementation's constructor fills
|
||||
* in the <var>$errorcode_map</var> property.
|
||||
*
|
||||
* @param string $nativecode error code returned by the database
|
||||
* @return int a portable DB error code, or DB_ERROR if this DB
|
||||
* implementation has no mapping for the given error code.
|
||||
*/
|
||||
function errorCode($nativecode)
|
||||
{
|
||||
if (ereg('SQLCODE=(.*)]', $nativecode, $match)) {
|
||||
$code = $match[1];
|
||||
if (isset($this->errorcode_map[$code])) {
|
||||
return $this->errorcode_map[$code];
|
||||
}
|
||||
}
|
||||
return DB_ERROR;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* NOTE: only supports 'table' if <var>$result</var> is a table name.
|
||||
*
|
||||
* If analyzing a query result and the result has duplicate field names,
|
||||
* an error will be raised saying
|
||||
* <samp>can't distinguish duplicate field names</samp>.
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
* @since Method available since Release 1.6.0
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
$id = @ifx_query("SELECT * FROM $result WHERE 1=0",
|
||||
$this->connection);
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
$flds = @ifx_fieldproperties($id);
|
||||
$count = @ifx_num_fields($id);
|
||||
|
||||
if (count($flds) != $count) {
|
||||
return $this->raiseError("can't distinguish duplicate field names");
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
foreach ($flds as $key => $value) {
|
||||
$props = explode(';', $value);
|
||||
$res[$i] = array(
|
||||
'table' => $got_string ? $case_func($result) : '',
|
||||
'name' => $case_func($key),
|
||||
'type' => $props[0],
|
||||
'len' => $props[1],
|
||||
'flags' => $props[4] == 'N' ? 'not_null' : '',
|
||||
);
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@ifx_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return string the SQL query string or null if the driver doesn't
|
||||
* support the object type requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'tables':
|
||||
return 'SELECT tabname FROM systables WHERE tabid >= 100';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
831
extlib/DB/msql.php
Normal file
831
extlib/DB/msql.php
Normal file
@ -0,0 +1,831 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's msql extension
|
||||
* for interacting with Mini SQL databases
|
||||
*
|
||||
* PHP's mSQL extension did weird things with NULL values prior to PHP
|
||||
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
|
||||
* those versions.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: msql.php,v 1.64 2007/09/21 13:40:41 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's msql extension
|
||||
* for interacting with Mini SQL databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* PHP's mSQL extension did weird things with NULL values prior to PHP
|
||||
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
|
||||
* those versions.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
* @since Class not functional until Release 1.7.0
|
||||
*/
|
||||
class DB_msql extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'msql';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'msql';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'emulate',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* The query result resource created by PHP
|
||||
*
|
||||
* Used to make affectedRows() work. Only contains the result for
|
||||
* data manipulation queries. Contains false for other queries.
|
||||
*
|
||||
* @var resource
|
||||
* @access private
|
||||
*/
|
||||
var $_result;
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_msql()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* Example of how to connect:
|
||||
* <code>
|
||||
* require_once 'DB.php';
|
||||
*
|
||||
* // $dsn = 'msql://hostname/dbname'; // use a TCP connection
|
||||
* $dsn = 'msql:///dbname'; // use a socket
|
||||
* $options = array(
|
||||
* 'portability' => DB_PORTABILITY_ALL,
|
||||
* );
|
||||
*
|
||||
* $db = DB::connect($dsn, $options);
|
||||
* if (PEAR::isError($db)) {
|
||||
* die($db->getMessage());
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('msql')) {
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
$params = array();
|
||||
if ($dsn['hostspec']) {
|
||||
$params[] = $dsn['port']
|
||||
? $dsn['hostspec'] . ',' . $dsn['port']
|
||||
: $dsn['hostspec'];
|
||||
}
|
||||
|
||||
$connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
|
||||
|
||||
$ini = ini_get('track_errors');
|
||||
$php_errormsg = '';
|
||||
if ($ini) {
|
||||
$this->connection = @call_user_func_array($connect_function,
|
||||
$params);
|
||||
} else {
|
||||
@ini_set('track_errors', 1);
|
||||
$this->connection = @call_user_func_array($connect_function,
|
||||
$params);
|
||||
@ini_set('track_errors', $ini);
|
||||
}
|
||||
|
||||
if (!$this->connection) {
|
||||
if (($err = @msql_error()) != '') {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$err);
|
||||
} else {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!@msql_select_db($dsn['database'], $this->connection)) {
|
||||
return $this->msqlRaiseError();
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @msql_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$this->last_query = $query;
|
||||
$query = $this->modifyQuery($query);
|
||||
$result = @msql_query($query, $this->connection);
|
||||
if (!$result) {
|
||||
return $this->msqlRaiseError();
|
||||
}
|
||||
// Determine which queries that should return data, and which
|
||||
// should return an error code only.
|
||||
if ($this->_checkManip($query)) {
|
||||
$this->_result = $result;
|
||||
return DB_OK;
|
||||
} else {
|
||||
$this->_result = false;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal msql result pointer to the next available result
|
||||
*
|
||||
* @param a valid fbsql result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* PHP's mSQL extension did weird things with NULL values prior to PHP
|
||||
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
|
||||
* those versions.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum !== null) {
|
||||
if (!@msql_data_seek($result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
$arr = @msql_fetch_array($result, MSQL_ASSOC);
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
$arr = @msql_fetch_row($result);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? msql_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @msql_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->msqlRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$rows = @msql_num_rows($result);
|
||||
if ($rows === false) {
|
||||
return $this->msqlRaiseError();
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affected()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if (!$this->_result) {
|
||||
return 0;
|
||||
}
|
||||
return msql_affected_rows($this->_result);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_msql::createSequence(), DB_msql::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
$repeat = false;
|
||||
do {
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query("SELECT _seq FROM ${seqname}");
|
||||
$this->popErrorHandling();
|
||||
if ($ondemand && DB::isError($result) &&
|
||||
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
|
||||
$repeat = true;
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->createSequence($seq_name);
|
||||
$this->popErrorHandling();
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
} else {
|
||||
$repeat = false;
|
||||
}
|
||||
} while ($repeat);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
$arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
|
||||
$result->free();
|
||||
return $arr[0];
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ createSequence()
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* Also creates a new table to associate the sequence with. Uses
|
||||
* a separate table to ensure portability with other drivers.
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_msql::nextID(), DB_msql::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
$res = $this->query('CREATE TABLE ' . $seqname
|
||||
. ' (id INTEGER NOT NULL)');
|
||||
if (DB::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$res = $this->query("CREATE SEQUENCE ON ${seqname}");
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_msql::nextID(), DB_msql::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteIdentifier()
|
||||
|
||||
/**
|
||||
* mSQL does not support delimited identifiers
|
||||
*
|
||||
* @param string $str the identifier name to be quoted
|
||||
*
|
||||
* @return object a DB_Error object
|
||||
*
|
||||
* @see DB_common::quoteIdentifier()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function quoteIdentifier($str)
|
||||
{
|
||||
return $this->raiseError(DB_ERROR_UNSUPPORTED);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteFloat()
|
||||
|
||||
/**
|
||||
* Formats a float value for use within a query in a locale-independent
|
||||
* manner.
|
||||
*
|
||||
* @param float the float value to be quoted.
|
||||
* @return string the quoted string.
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since release 1.7.8.
|
||||
*/
|
||||
function quoteFloat($float) {
|
||||
return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ escapeSimple()
|
||||
|
||||
/**
|
||||
* Escapes a string according to the current DBMS's standards
|
||||
*
|
||||
* @param string $str the string to be escaped
|
||||
*
|
||||
* @return string the escaped string
|
||||
*
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function escapeSimple($str)
|
||||
{
|
||||
return addslashes($str);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ msqlRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_msql::errorNative(), DB_msql::errorCode()
|
||||
*/
|
||||
function msqlRaiseError($errno = null)
|
||||
{
|
||||
$native = $this->errorNative();
|
||||
if ($errno === null) {
|
||||
$errno = $this->errorCode($native);
|
||||
}
|
||||
return $this->raiseError($errno, null, null, null, $native);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error message produced by the last query
|
||||
*
|
||||
* @return string the DBMS' error message
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
return @msql_error();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorCode()
|
||||
|
||||
/**
|
||||
* Determines PEAR::DB error code from the database's text error message
|
||||
*
|
||||
* @param string $errormsg the error message returned from the database
|
||||
*
|
||||
* @return integer the error number from a DB_ERROR* constant
|
||||
*/
|
||||
function errorCode($errormsg)
|
||||
{
|
||||
static $error_regexps;
|
||||
|
||||
// PHP 5.2+ prepends the function name to $php_errormsg, so we need
|
||||
// this hack to work around it, per bug #9599.
|
||||
$errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg);
|
||||
|
||||
if (!isset($error_regexps)) {
|
||||
$error_regexps = array(
|
||||
'/^Access to database denied/i'
|
||||
=> DB_ERROR_ACCESS_VIOLATION,
|
||||
'/^Bad index name/i'
|
||||
=> DB_ERROR_ALREADY_EXISTS,
|
||||
'/^Bad order field/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Bad type for comparison/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Can\'t perform LIKE on/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Can\'t use TEXT fields in LIKE comparison/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Couldn\'t create temporary table/i'
|
||||
=> DB_ERROR_CANNOT_CREATE,
|
||||
'/^Error creating table file/i'
|
||||
=> DB_ERROR_CANNOT_CREATE,
|
||||
'/^Field .* cannot be null$/i'
|
||||
=> DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'/^Index (field|condition) .* cannot be null$/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Invalid date format/i'
|
||||
=> DB_ERROR_INVALID_DATE,
|
||||
'/^Invalid time format/i'
|
||||
=> DB_ERROR_INVALID,
|
||||
'/^Literal value for .* is wrong type$/i'
|
||||
=> DB_ERROR_INVALID_NUMBER,
|
||||
'/^No Database Selected/i'
|
||||
=> DB_ERROR_NODBSELECTED,
|
||||
'/^No value specified for field/i'
|
||||
=> DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
'/^Non unique value for unique index/i'
|
||||
=> DB_ERROR_CONSTRAINT,
|
||||
'/^Out of memory for temporary table/i'
|
||||
=> DB_ERROR_CANNOT_CREATE,
|
||||
'/^Permission denied/i'
|
||||
=> DB_ERROR_ACCESS_VIOLATION,
|
||||
'/^Reference to un-selected table/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^syntax error/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Table .* exists$/i'
|
||||
=> DB_ERROR_ALREADY_EXISTS,
|
||||
'/^Unknown database/i'
|
||||
=> DB_ERROR_NOSUCHDB,
|
||||
'/^Unknown field/i'
|
||||
=> DB_ERROR_NOSUCHFIELD,
|
||||
'/^Unknown (index|system variable)/i'
|
||||
=> DB_ERROR_NOT_FOUND,
|
||||
'/^Unknown table/i'
|
||||
=> DB_ERROR_NOSUCHTABLE,
|
||||
'/^Unqualified field/i'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($error_regexps as $regexp => $code) {
|
||||
if (preg_match($regexp, $errormsg)) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
return DB_ERROR;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::setOption()
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
$id = @msql_query("SELECT * FROM $result",
|
||||
$this->connection);
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
* Deprecated. Here for compatibility only.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = @msql_num_fields($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$tmp = @msql_fetch_field($id);
|
||||
|
||||
$flags = '';
|
||||
if ($tmp->not_null) {
|
||||
$flags .= 'not_null ';
|
||||
}
|
||||
if ($tmp->unique) {
|
||||
$flags .= 'unique_key ';
|
||||
}
|
||||
$flags = trim($flags);
|
||||
|
||||
$res[$i] = array(
|
||||
'table' => $case_func($tmp->table),
|
||||
'name' => $case_func($tmp->name),
|
||||
'type' => $tmp->type,
|
||||
'len' => msql_field_len($id, $i),
|
||||
'flags' => $flags,
|
||||
);
|
||||
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@msql_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtain a list of a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return array the array containing the list of objects requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'databases':
|
||||
$id = @msql_list_dbs($this->connection);
|
||||
break;
|
||||
case 'tables':
|
||||
$id = @msql_list_tables($this->dsn['database'],
|
||||
$this->connection);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
if (!$id) {
|
||||
return $this->msqlRaiseError();
|
||||
}
|
||||
$out = array();
|
||||
while ($row = @msql_fetch_row($id)) {
|
||||
$out[] = $row[0];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
963
extlib/DB/mssql.php
Normal file
963
extlib/DB/mssql.php
Normal file
@ -0,0 +1,963 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's mssql extension
|
||||
* for interacting with Microsoft SQL Server databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Sterling Hughes <sterling@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: mssql.php,v 1.92 2007/09/21 13:40:41 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's mssql extension
|
||||
* for interacting with Microsoft SQL Server databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* DB's mssql driver is only for Microsfoft SQL Server databases.
|
||||
*
|
||||
* If you're connecting to a Sybase database, you MUST specify "sybase"
|
||||
* as the "phptype" in the DSN.
|
||||
*
|
||||
* This class only works correctly if you have compiled PHP using
|
||||
* --with-mssql=[dir_to_FreeTDS].
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Sterling Hughes <sterling@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_mssql extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'mssql';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'mssql';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'emulate',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
// XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
|
||||
var $errorcode_map = array(
|
||||
102 => DB_ERROR_SYNTAX,
|
||||
110 => DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
155 => DB_ERROR_NOSUCHFIELD,
|
||||
156 => DB_ERROR_SYNTAX,
|
||||
170 => DB_ERROR_SYNTAX,
|
||||
207 => DB_ERROR_NOSUCHFIELD,
|
||||
208 => DB_ERROR_NOSUCHTABLE,
|
||||
245 => DB_ERROR_INVALID_NUMBER,
|
||||
319 => DB_ERROR_SYNTAX,
|
||||
321 => DB_ERROR_NOSUCHFIELD,
|
||||
325 => DB_ERROR_SYNTAX,
|
||||
336 => DB_ERROR_SYNTAX,
|
||||
515 => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
547 => DB_ERROR_CONSTRAINT,
|
||||
1018 => DB_ERROR_SYNTAX,
|
||||
1035 => DB_ERROR_SYNTAX,
|
||||
1913 => DB_ERROR_ALREADY_EXISTS,
|
||||
2209 => DB_ERROR_SYNTAX,
|
||||
2223 => DB_ERROR_SYNTAX,
|
||||
2248 => DB_ERROR_SYNTAX,
|
||||
2256 => DB_ERROR_SYNTAX,
|
||||
2257 => DB_ERROR_SYNTAX,
|
||||
2627 => DB_ERROR_CONSTRAINT,
|
||||
2714 => DB_ERROR_ALREADY_EXISTS,
|
||||
3607 => DB_ERROR_DIVZERO,
|
||||
3701 => DB_ERROR_NOSUCHTABLE,
|
||||
7630 => DB_ERROR_SYNTAX,
|
||||
8134 => DB_ERROR_DIVZERO,
|
||||
9303 => DB_ERROR_SYNTAX,
|
||||
9317 => DB_ERROR_SYNTAX,
|
||||
9318 => DB_ERROR_SYNTAX,
|
||||
9331 => DB_ERROR_SYNTAX,
|
||||
9332 => DB_ERROR_SYNTAX,
|
||||
15253 => DB_ERROR_SYNTAX,
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* Should data manipulation queries be committed automatically?
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
var $autocommit = true;
|
||||
|
||||
/**
|
||||
* The quantity of transactions begun
|
||||
*
|
||||
* {@internal While this is private, it can't actually be designated
|
||||
* private in PHP 5 because it is directly accessed in the test suite.}}
|
||||
*
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
var $transaction_opcount = 0;
|
||||
|
||||
/**
|
||||
* The database specified in the DSN
|
||||
*
|
||||
* It's a fix to allow calls to different databases in the same script.
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
var $_db = null;
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_mssql()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase')
|
||||
&& !PEAR::loadExtension('sybase_ct'))
|
||||
{
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
$params = array(
|
||||
$dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
|
||||
$dsn['username'] ? $dsn['username'] : null,
|
||||
$dsn['password'] ? $dsn['password'] : null,
|
||||
);
|
||||
if ($dsn['port']) {
|
||||
$params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':')
|
||||
. $dsn['port'];
|
||||
}
|
||||
|
||||
$connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
|
||||
|
||||
$this->connection = @call_user_func_array($connect_function, $params);
|
||||
|
||||
if (!$this->connection) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
@mssql_get_last_message());
|
||||
}
|
||||
if ($dsn['database']) {
|
||||
if (!@mssql_select_db($dsn['database'], $this->connection)) {
|
||||
return $this->raiseError(DB_ERROR_NODBSELECTED,
|
||||
null, null, null,
|
||||
@mssql_get_last_message());
|
||||
}
|
||||
$this->_db = $dsn['database'];
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @mssql_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$ismanip = $this->_checkManip($query);
|
||||
$this->last_query = $query;
|
||||
if (!@mssql_select_db($this->_db, $this->connection)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$query = $this->modifyQuery($query);
|
||||
if (!$this->autocommit && $ismanip) {
|
||||
if ($this->transaction_opcount == 0) {
|
||||
$result = @mssql_query('BEGIN TRAN', $this->connection);
|
||||
if (!$result) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
}
|
||||
$this->transaction_opcount++;
|
||||
}
|
||||
$result = @mssql_query($query, $this->connection);
|
||||
if (!$result) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
// Determine which queries that should return data, and which
|
||||
// should return an error code only.
|
||||
return $ismanip ? DB_OK : $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal mssql result pointer to the next available result
|
||||
*
|
||||
* @param a valid fbsql result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return @mssql_next_result($result);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum !== null) {
|
||||
if (!@mssql_data_seek($result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
$arr = @mssql_fetch_assoc($result);
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
$arr = @mssql_fetch_row($result);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? mssql_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @mssql_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$rows = @mssql_num_rows($result);
|
||||
if ($rows === false) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ autoCommit()
|
||||
|
||||
/**
|
||||
* Enables or disables automatic commits
|
||||
*
|
||||
* @param bool $onoff true turns it on, false turns it off
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object if the driver
|
||||
* doesn't support auto-committing transactions.
|
||||
*/
|
||||
function autoCommit($onoff = false)
|
||||
{
|
||||
// XXX if $this->transaction_opcount > 0, we should probably
|
||||
// issue a warning here.
|
||||
$this->autocommit = $onoff ? true : false;
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ commit()
|
||||
|
||||
/**
|
||||
* Commits the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function commit()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
if (!@mssql_select_db($this->_db, $this->connection)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$result = @mssql_query('COMMIT TRAN', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ rollback()
|
||||
|
||||
/**
|
||||
* Reverts the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function rollback()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
if (!@mssql_select_db($this->_db, $this->connection)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$result = @mssql_query('ROLLBACK TRAN', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affectedRows()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if ($this->_last_query_manip) {
|
||||
$res = @mssql_query('select @@rowcount', $this->connection);
|
||||
if (!$res) {
|
||||
return $this->mssqlRaiseError();
|
||||
}
|
||||
$ar = @mssql_fetch_row($res);
|
||||
if (!$ar) {
|
||||
$result = 0;
|
||||
} else {
|
||||
@mssql_free_result($res);
|
||||
$result = $ar[0];
|
||||
}
|
||||
} else {
|
||||
$result = 0;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_mssql::createSequence(), DB_mssql::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
if (!@mssql_select_db($this->_db, $this->connection)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$repeat = 0;
|
||||
do {
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
|
||||
$this->popErrorHandling();
|
||||
if ($ondemand && DB::isError($result) &&
|
||||
($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
|
||||
{
|
||||
$repeat = 1;
|
||||
$result = $this->createSequence($seq_name);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
} elseif (!DB::isError($result)) {
|
||||
$result = $this->query("SELECT IDENT_CURRENT('$seqname')");
|
||||
if (DB::isError($result)) {
|
||||
/* Fallback code for MS SQL Server 7.0, which doesn't have
|
||||
* IDENT_CURRENT. This is *not* safe for concurrent
|
||||
* requests, and really, if you're using it, you're in a
|
||||
* world of hurt. Nevertheless, it's here to ensure BC. See
|
||||
* bug #181 for the gory details.*/
|
||||
$result = $this->query("SELECT @@IDENTITY FROM $seqname");
|
||||
}
|
||||
$repeat = 0;
|
||||
} else {
|
||||
$repeat = false;
|
||||
}
|
||||
} while ($repeat);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
$result = $result->fetchRow(DB_FETCHMODE_ORDERED);
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_mssql::nextID(), DB_mssql::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
return $this->query('CREATE TABLE '
|
||||
. $this->getSequenceName($seq_name)
|
||||
. ' ([id] [int] IDENTITY (1, 1) NOT NULL,'
|
||||
. ' [vapor] [int] NULL)');
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_mssql::nextID(), DB_mssql::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteIdentifier()
|
||||
|
||||
/**
|
||||
* Quotes a string so it can be safely used as a table or column name
|
||||
*
|
||||
* @param string $str identifier name to be quoted
|
||||
*
|
||||
* @return string quoted identifier string
|
||||
*
|
||||
* @see DB_common::quoteIdentifier()
|
||||
* @since Method available since Release 1.6.0
|
||||
*/
|
||||
function quoteIdentifier($str)
|
||||
{
|
||||
return '[' . str_replace(']', ']]', $str) . ']';
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ mssqlRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_mssql::errorNative(), DB_mssql::errorCode()
|
||||
*/
|
||||
function mssqlRaiseError($code = null)
|
||||
{
|
||||
$message = @mssql_get_last_message();
|
||||
if (!$code) {
|
||||
$code = $this->errorNative();
|
||||
}
|
||||
return $this->raiseError($this->errorCode($code, $message),
|
||||
null, null, null, "$code - $message");
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error code produced by the last query
|
||||
*
|
||||
* @return int the DBMS' error code
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
$res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
|
||||
if (!$res) {
|
||||
return DB_ERROR;
|
||||
}
|
||||
$row = @mssql_fetch_row($res);
|
||||
return $row[0];
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorCode()
|
||||
|
||||
/**
|
||||
* Determines PEAR::DB error code from mssql's native codes.
|
||||
*
|
||||
* If <var>$nativecode</var> isn't known yet, it will be looked up.
|
||||
*
|
||||
* @param mixed $nativecode mssql error code, if known
|
||||
* @return integer an error number from a DB error constant
|
||||
* @see errorNative()
|
||||
*/
|
||||
function errorCode($nativecode = null, $msg = '')
|
||||
{
|
||||
if (!$nativecode) {
|
||||
$nativecode = $this->errorNative();
|
||||
}
|
||||
if (isset($this->errorcode_map[$nativecode])) {
|
||||
if ($nativecode == 3701
|
||||
&& preg_match('/Cannot drop the index/i', $msg))
|
||||
{
|
||||
return DB_ERROR_NOT_FOUND;
|
||||
}
|
||||
return $this->errorcode_map[$nativecode];
|
||||
} else {
|
||||
return DB_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* NOTE: only supports 'table' and 'flags' if <var>$result</var>
|
||||
* is a table name.
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
if (!@mssql_select_db($this->_db, $this->connection)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$id = @mssql_query("SELECT * FROM $result WHERE 1=0",
|
||||
$this->connection);
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
* Deprecated. Here for compatibility only.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = @mssql_num_fields($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($got_string) {
|
||||
$flags = $this->_mssql_field_flags($result,
|
||||
@mssql_field_name($id, $i));
|
||||
if (DB::isError($flags)) {
|
||||
return $flags;
|
||||
}
|
||||
} else {
|
||||
$flags = '';
|
||||
}
|
||||
|
||||
$res[$i] = array(
|
||||
'table' => $got_string ? $case_func($result) : '',
|
||||
'name' => $case_func(@mssql_field_name($id, $i)),
|
||||
'type' => @mssql_field_type($id, $i),
|
||||
'len' => @mssql_field_length($id, $i),
|
||||
'flags' => $flags,
|
||||
);
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@mssql_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _mssql_field_flags()
|
||||
|
||||
/**
|
||||
* Get a column's flags
|
||||
*
|
||||
* Supports "not_null", "primary_key",
|
||||
* "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
|
||||
* "unique_key" (mssql unique index, unique check or primary_key) and
|
||||
* "multiple_key" (multikey index)
|
||||
*
|
||||
* mssql timestamp is NOT similar to the mysql timestamp so this is maybe
|
||||
* not useful at all - is the behaviour of mysql_field_flags that primary
|
||||
* keys are alway unique? is the interpretation of multiple_key correct?
|
||||
*
|
||||
* @param string $table the table name
|
||||
* @param string $column the field name
|
||||
*
|
||||
* @return string the flags
|
||||
*
|
||||
* @access private
|
||||
* @author Joern Barthel <j_barthel@web.de>
|
||||
*/
|
||||
function _mssql_field_flags($table, $column)
|
||||
{
|
||||
static $tableName = null;
|
||||
static $flags = array();
|
||||
|
||||
if ($table != $tableName) {
|
||||
|
||||
$flags = array();
|
||||
$tableName = $table;
|
||||
|
||||
// get unique and primary keys
|
||||
$res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC);
|
||||
if (DB::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
foreach ($res as $val) {
|
||||
$keys = explode(', ', $val['index_keys']);
|
||||
|
||||
if (sizeof($keys) > 1) {
|
||||
foreach ($keys as $key) {
|
||||
$this->_add_flag($flags[$key], 'multiple_key');
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($val['index_description'], 'primary key')) {
|
||||
foreach ($keys as $key) {
|
||||
$this->_add_flag($flags[$key], 'primary_key');
|
||||
}
|
||||
} elseif (strpos($val['index_description'], 'unique')) {
|
||||
foreach ($keys as $key) {
|
||||
$this->_add_flag($flags[$key], 'unique_key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get auto_increment, not_null and timestamp
|
||||
$res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC);
|
||||
if (DB::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
foreach ($res as $val) {
|
||||
$val = array_change_key_case($val, CASE_LOWER);
|
||||
if ($val['nullable'] == '0') {
|
||||
$this->_add_flag($flags[$val['column_name']], 'not_null');
|
||||
}
|
||||
if (strpos($val['type_name'], 'identity')) {
|
||||
$this->_add_flag($flags[$val['column_name']], 'auto_increment');
|
||||
}
|
||||
if (strpos($val['type_name'], 'timestamp')) {
|
||||
$this->_add_flag($flags[$val['column_name']], 'timestamp');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($column, $flags)) {
|
||||
return(implode(' ', $flags[$column]));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _add_flag()
|
||||
|
||||
/**
|
||||
* Adds a string to the flags array if the flag is not yet in there
|
||||
* - if there is no flag present the array is created
|
||||
*
|
||||
* @param array &$array the reference to the flag-array
|
||||
* @param string $value the flag value
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @access private
|
||||
* @author Joern Barthel <j_barthel@web.de>
|
||||
*/
|
||||
function _add_flag(&$array, $value)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
$array = array($value);
|
||||
} elseif (!in_array($value, $array)) {
|
||||
array_push($array, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return string the SQL query string or null if the driver doesn't
|
||||
* support the object type requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'tables':
|
||||
return "SELECT name FROM sysobjects WHERE type = 'U'"
|
||||
. ' ORDER BY name';
|
||||
case 'views':
|
||||
return "SELECT name FROM sysobjects WHERE type = 'V'";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
1045
extlib/DB/mysql.php
Normal file
1045
extlib/DB/mysql.php
Normal file
File diff suppressed because it is too large
Load Diff
1092
extlib/DB/mysqli.php
Normal file
1092
extlib/DB/mysqli.php
Normal file
File diff suppressed because it is too large
Load Diff
1156
extlib/DB/oci8.php
Normal file
1156
extlib/DB/oci8.php
Normal file
File diff suppressed because it is too large
Load Diff
883
extlib/DB/odbc.php
Normal file
883
extlib/DB/odbc.php
Normal file
@ -0,0 +1,883 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's odbc extension
|
||||
* for interacting with databases via ODBC connections
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Stig Bakken <ssb@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: odbc.php,v 1.81 2007/07/06 05:19:21 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's odbc extension
|
||||
* for interacting with databases via ODBC connections
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* More info on ODBC errors could be found here:
|
||||
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Stig Bakken <ssb@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_odbc extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'odbc';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'sql92';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* NOTE: The feature set of the following drivers are different than
|
||||
* the default:
|
||||
* + solid: 'transactions' = true
|
||||
* + navision: 'limit' = false
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'emulate',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
'01004' => DB_ERROR_TRUNCATED,
|
||||
'07001' => DB_ERROR_MISMATCH,
|
||||
'21S01' => DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
'21S02' => DB_ERROR_MISMATCH,
|
||||
'22001' => DB_ERROR_INVALID,
|
||||
'22003' => DB_ERROR_INVALID_NUMBER,
|
||||
'22005' => DB_ERROR_INVALID_NUMBER,
|
||||
'22008' => DB_ERROR_INVALID_DATE,
|
||||
'22012' => DB_ERROR_DIVZERO,
|
||||
'23000' => DB_ERROR_CONSTRAINT,
|
||||
'23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'23503' => DB_ERROR_CONSTRAINT,
|
||||
'23504' => DB_ERROR_CONSTRAINT,
|
||||
'23505' => DB_ERROR_CONSTRAINT,
|
||||
'24000' => DB_ERROR_INVALID,
|
||||
'34000' => DB_ERROR_INVALID,
|
||||
'37000' => DB_ERROR_SYNTAX,
|
||||
'42000' => DB_ERROR_SYNTAX,
|
||||
'42601' => DB_ERROR_SYNTAX,
|
||||
'IM001' => DB_ERROR_UNSUPPORTED,
|
||||
'S0000' => DB_ERROR_NOSUCHTABLE,
|
||||
'S0001' => DB_ERROR_ALREADY_EXISTS,
|
||||
'S0002' => DB_ERROR_NOSUCHTABLE,
|
||||
'S0011' => DB_ERROR_ALREADY_EXISTS,
|
||||
'S0012' => DB_ERROR_NOT_FOUND,
|
||||
'S0021' => DB_ERROR_ALREADY_EXISTS,
|
||||
'S0022' => DB_ERROR_NOSUCHFIELD,
|
||||
'S1009' => DB_ERROR_INVALID,
|
||||
'S1090' => DB_ERROR_INVALID,
|
||||
'S1C00' => DB_ERROR_NOT_CAPABLE,
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* The number of rows affected by a data manipulation query
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
var $affected = 0;
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_odbc()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* PEAR DB's odbc driver supports the following extra DSN options:
|
||||
* + cursor The type of cursor to be used for this connection.
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('odbc')) {
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
switch ($this->dbsyntax) {
|
||||
case 'access':
|
||||
case 'db2':
|
||||
case 'solid':
|
||||
$this->features['transactions'] = true;
|
||||
break;
|
||||
case 'navision':
|
||||
$this->features['limit'] = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is hear for backwards compatibility. Should have been using
|
||||
* 'database' all along, but prior to 1.6.0RC3 'hostspec' was used.
|
||||
*/
|
||||
if ($dsn['database']) {
|
||||
$odbcdsn = $dsn['database'];
|
||||
} elseif ($dsn['hostspec']) {
|
||||
$odbcdsn = $dsn['hostspec'];
|
||||
} else {
|
||||
$odbcdsn = 'localhost';
|
||||
}
|
||||
|
||||
$connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
|
||||
|
||||
if (empty($dsn['cursor'])) {
|
||||
$this->connection = @$connect_function($odbcdsn, $dsn['username'],
|
||||
$dsn['password']);
|
||||
} else {
|
||||
$this->connection = @$connect_function($odbcdsn, $dsn['username'],
|
||||
$dsn['password'],
|
||||
$dsn['cursor']);
|
||||
}
|
||||
|
||||
if (!is_resource($this->connection)) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
$this->errorNative());
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$err = @odbc_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $err;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$this->last_query = $query;
|
||||
$query = $this->modifyQuery($query);
|
||||
$result = @odbc_exec($this->connection, $query);
|
||||
if (!$result) {
|
||||
return $this->odbcRaiseError(); // XXX ERRORMSG
|
||||
}
|
||||
// Determine which queries that should return data, and which
|
||||
// should return an error code only.
|
||||
if ($this->_checkManip($query)) {
|
||||
$this->affected = $result; // For affectedRows()
|
||||
return DB_OK;
|
||||
}
|
||||
$this->affected = 0;
|
||||
return $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal odbc result pointer to the next available result
|
||||
*
|
||||
* @param a valid fbsql result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return @odbc_next_result($result);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
$arr = array();
|
||||
if ($rownum !== null) {
|
||||
$rownum++; // ODBC first row is 1
|
||||
if (version_compare(phpversion(), '4.2.0', 'ge')) {
|
||||
$cols = @odbc_fetch_into($result, $arr, $rownum);
|
||||
} else {
|
||||
$cols = @odbc_fetch_into($result, $rownum, $arr);
|
||||
}
|
||||
} else {
|
||||
$cols = @odbc_fetch_into($result, $arr);
|
||||
}
|
||||
if (!$cols) {
|
||||
return null;
|
||||
}
|
||||
if ($fetchmode !== DB_FETCHMODE_ORDERED) {
|
||||
for ($i = 0; $i < count($arr); $i++) {
|
||||
$colName = @odbc_field_name($result, $i+1);
|
||||
$a[$colName] = $arr[$i];
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$a = array_change_key_case($a, CASE_LOWER);
|
||||
}
|
||||
$arr = $a;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? odbc_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @odbc_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affectedRows()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if (empty($this->affected)) { // In case of SELECT stms
|
||||
return 0;
|
||||
}
|
||||
$nrows = @odbc_num_rows($this->affected);
|
||||
if ($nrows == -1) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return $nrows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* Not all ODBC drivers support this functionality. If they don't
|
||||
* a DB_Error object for DB_ERROR_UNSUPPORTED is returned.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$nrows = @odbc_num_rows($result);
|
||||
if ($nrows == -1) {
|
||||
return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
|
||||
}
|
||||
if ($nrows === false) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return $nrows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteIdentifier()
|
||||
|
||||
/**
|
||||
* Quotes a string so it can be safely used as a table or column name
|
||||
*
|
||||
* Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
|
||||
* "Use ANSI quoted identifiers" when setting up the ODBC data source.
|
||||
*
|
||||
* @param string $str identifier name to be quoted
|
||||
*
|
||||
* @return string quoted identifier string
|
||||
*
|
||||
* @see DB_common::quoteIdentifier()
|
||||
* @since Method available since Release 1.6.0
|
||||
*/
|
||||
function quoteIdentifier($str)
|
||||
{
|
||||
switch ($this->dsn['dbsyntax']) {
|
||||
case 'access':
|
||||
return '[' . $str . ']';
|
||||
case 'mssql':
|
||||
case 'sybase':
|
||||
return '[' . str_replace(']', ']]', $str) . ']';
|
||||
case 'mysql':
|
||||
case 'mysqli':
|
||||
return '`' . $str . '`';
|
||||
default:
|
||||
return '"' . str_replace('"', '""', $str) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quote()
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in release 1.6.0
|
||||
* @internal
|
||||
*/
|
||||
function quote($str)
|
||||
{
|
||||
return $this->quoteSmart($str);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_odbc::createSequence(), DB_odbc::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
$repeat = 0;
|
||||
do {
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query("update ${seqname} set id = id + 1");
|
||||
$this->popErrorHandling();
|
||||
if ($ondemand && DB::isError($result) &&
|
||||
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
|
||||
$repeat = 1;
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->createSequence($seq_name);
|
||||
$this->popErrorHandling();
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
$result = $this->query("insert into ${seqname} (id) values(0)");
|
||||
} else {
|
||||
$repeat = 0;
|
||||
}
|
||||
} while ($repeat);
|
||||
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
|
||||
$result = $this->query("select id from ${seqname}");
|
||||
if (DB::isError($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$row = $result->fetchRow(DB_FETCHMODE_ORDERED);
|
||||
if (DB::isError($row || !$row)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
return $row[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_odbc::nextID(), DB_odbc::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
return $this->query('CREATE TABLE '
|
||||
. $this->getSequenceName($seq_name)
|
||||
. ' (id integer NOT NULL,'
|
||||
. ' PRIMARY KEY(id))');
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_odbc::nextID(), DB_odbc::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ autoCommit()
|
||||
|
||||
/**
|
||||
* Enables or disables automatic commits
|
||||
*
|
||||
* @param bool $onoff true turns it on, false turns it off
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object if the driver
|
||||
* doesn't support auto-committing transactions.
|
||||
*/
|
||||
function autoCommit($onoff = false)
|
||||
{
|
||||
if (!@odbc_autocommit($this->connection, $onoff)) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ commit()
|
||||
|
||||
/**
|
||||
* Commits the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function commit()
|
||||
{
|
||||
if (!@odbc_commit($this->connection)) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ rollback()
|
||||
|
||||
/**
|
||||
* Reverts the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function rollback()
|
||||
{
|
||||
if (!@odbc_rollback($this->connection)) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ odbcRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_odbc::errorNative(), DB_common::errorCode()
|
||||
*/
|
||||
function odbcRaiseError($errno = null)
|
||||
{
|
||||
if ($errno === null) {
|
||||
switch ($this->dbsyntax) {
|
||||
case 'access':
|
||||
if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
|
||||
$this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
|
||||
} else {
|
||||
// Doing this in case mode changes during runtime.
|
||||
$this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
|
||||
}
|
||||
|
||||
$native_code = odbc_error($this->connection);
|
||||
|
||||
// S1000 is for "General Error." Let's be more specific.
|
||||
if ($native_code == 'S1000') {
|
||||
$errormsg = odbc_errormsg($this->connection);
|
||||
static $error_regexps;
|
||||
if (!isset($error_regexps)) {
|
||||
$error_regexps = array(
|
||||
'/includes related records.$/i' => DB_ERROR_CONSTRAINT,
|
||||
'/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
);
|
||||
}
|
||||
foreach ($error_regexps as $regexp => $code) {
|
||||
if (preg_match($regexp, $errormsg)) {
|
||||
return $this->raiseError($code,
|
||||
null, null, null,
|
||||
$native_code . ' ' . $errormsg);
|
||||
}
|
||||
}
|
||||
$errno = DB_ERROR;
|
||||
} else {
|
||||
$errno = $this->errorCode($native_code);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$errno = $this->errorCode(odbc_error($this->connection));
|
||||
}
|
||||
}
|
||||
return $this->raiseError($errno, null, null, null,
|
||||
$this->errorNative());
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error code and message produced by the last query
|
||||
*
|
||||
* @return string the DBMS' error code and message
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
if (!is_resource($this->connection)) {
|
||||
return @odbc_error() . ' ' . @odbc_errormsg();
|
||||
}
|
||||
return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
$id = @odbc_exec($this->connection, "SELECT * FROM $result");
|
||||
if (!$id) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
* Deprecated. Here for compatibility only.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = @odbc_num_fields($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$col = $i + 1;
|
||||
$res[$i] = array(
|
||||
'table' => $got_string ? $case_func($result) : '',
|
||||
'name' => $case_func(@odbc_field_name($id, $col)),
|
||||
'type' => @odbc_field_type($id, $col),
|
||||
'len' => @odbc_field_len($id, $col),
|
||||
'flags' => '',
|
||||
);
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@odbc_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com.
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return string the list of objects requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'databases':
|
||||
if (!function_exists('odbc_data_source')) {
|
||||
return null;
|
||||
}
|
||||
$res = @odbc_data_source($this->connection, SQL_FETCH_FIRST);
|
||||
if (is_array($res)) {
|
||||
$out = array($res['server']);
|
||||
while($res = @odbc_data_source($this->connection,
|
||||
SQL_FETCH_NEXT))
|
||||
{
|
||||
$out[] = $res['server'];
|
||||
}
|
||||
return $out;
|
||||
} else {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
break;
|
||||
case 'tables':
|
||||
case 'schema.tables':
|
||||
$keep = 'TABLE';
|
||||
break;
|
||||
case 'views':
|
||||
$keep = 'VIEW';
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removing non-conforming items in the while loop rather than
|
||||
* in the odbc_tables() call because some backends choke on this:
|
||||
* odbc_tables($this->connection, '', '', '', 'TABLE')
|
||||
*/
|
||||
$res = @odbc_tables($this->connection);
|
||||
if (!$res) {
|
||||
return $this->odbcRaiseError();
|
||||
}
|
||||
$out = array();
|
||||
while ($row = odbc_fetch_array($res)) {
|
||||
if ($row['TABLE_TYPE'] != $keep) {
|
||||
continue;
|
||||
}
|
||||
if ($type == 'schema.tables') {
|
||||
$out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME'];
|
||||
} else {
|
||||
$out[] = $row['TABLE_NAME'];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
1135
extlib/DB/pgsql.php
Normal file
1135
extlib/DB/pgsql.php
Normal file
File diff suppressed because it is too large
Load Diff
960
extlib/DB/sqlite.php
Normal file
960
extlib/DB/sqlite.php
Normal file
@ -0,0 +1,960 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's sqlite extension
|
||||
* for interacting with SQLite databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Urs Gehrig <urs@circle.ch>
|
||||
* @author Mika Tuupola <tuupola@appelsiini.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
|
||||
* @version CVS: $Id: sqlite.php,v 1.118 2007/11/26 22:57:18 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's sqlite extension
|
||||
* for interacting with SQLite databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* NOTICE: This driver needs PHP's track_errors ini setting to be on.
|
||||
* It is automatically turned on when connecting to the database.
|
||||
* Make sure your scripts don't turn it off.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Urs Gehrig <urs@circle.ch>
|
||||
* @author Mika Tuupola <tuupola@appelsiini.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_sqlite extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'sqlite';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'sqlite';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'alter',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
*
|
||||
* {@internal Error codes according to sqlite_exec. See the online
|
||||
* manual at http://sqlite.org/c_interface.html for info.
|
||||
* This error handling based on sqlite_exec is not yet implemented.}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* SQLite data types
|
||||
*
|
||||
* @link http://www.sqlite.org/datatypes.html
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $keywords = array (
|
||||
'BLOB' => '',
|
||||
'BOOLEAN' => '',
|
||||
'CHARACTER' => '',
|
||||
'CLOB' => '',
|
||||
'FLOAT' => '',
|
||||
'INTEGER' => '',
|
||||
'KEY' => '',
|
||||
'NATIONAL' => '',
|
||||
'NUMERIC' => '',
|
||||
'NVARCHAR' => '',
|
||||
'PRIMARY' => '',
|
||||
'TEXT' => '',
|
||||
'TIMESTAMP' => '',
|
||||
'UNIQUE' => '',
|
||||
'VARCHAR' => '',
|
||||
'VARYING' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* The most recent error message from $php_errormsg
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
var $_lasterror = '';
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_sqlite()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* PEAR DB's sqlite driver supports the following extra DSN options:
|
||||
* + mode The permissions for the database file, in four digit
|
||||
* chmod octal format (eg "0600").
|
||||
*
|
||||
* Example of connecting to a database in read-only mode:
|
||||
* <code>
|
||||
* require_once 'DB.php';
|
||||
*
|
||||
* $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400';
|
||||
* $options = array(
|
||||
* 'portability' => DB_PORTABILITY_ALL,
|
||||
* );
|
||||
*
|
||||
* $db = DB::connect($dsn, $options);
|
||||
* if (PEAR::isError($db)) {
|
||||
* die($db->getMessage());
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('sqlite')) {
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
if (!$dsn['database']) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
|
||||
}
|
||||
|
||||
if ($dsn['database'] !== ':memory:') {
|
||||
if (!file_exists($dsn['database'])) {
|
||||
if (!touch($dsn['database'])) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
|
||||
}
|
||||
if (!isset($dsn['mode']) ||
|
||||
!is_numeric($dsn['mode']))
|
||||
{
|
||||
$mode = 0644;
|
||||
} else {
|
||||
$mode = octdec($dsn['mode']);
|
||||
}
|
||||
if (!chmod($dsn['database'], $mode)) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
|
||||
}
|
||||
if (!file_exists($dsn['database'])) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
if (!is_file($dsn['database'])) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_INVALID);
|
||||
}
|
||||
if (!is_readable($dsn['database'])) {
|
||||
return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
|
||||
}
|
||||
}
|
||||
|
||||
$connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
|
||||
|
||||
// track_errors must remain on for simpleQuery()
|
||||
@ini_set('track_errors', 1);
|
||||
$php_errormsg = '';
|
||||
|
||||
if (!$this->connection = @$connect_function($dsn['database'])) {
|
||||
return $this->raiseError(DB_ERROR_NODBSELECTED,
|
||||
null, null, null,
|
||||
$php_errormsg);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @sqlite_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* NOTICE: This method needs PHP's track_errors ini setting to be on.
|
||||
* It is automatically turned on when connecting to the database.
|
||||
* Make sure your scripts don't turn it off.
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$ismanip = $this->_checkManip($query);
|
||||
$this->last_query = $query;
|
||||
$query = $this->modifyQuery($query);
|
||||
|
||||
$php_errormsg = '';
|
||||
|
||||
$result = @sqlite_query($query, $this->connection);
|
||||
$this->_lasterror = $php_errormsg ? $php_errormsg : '';
|
||||
|
||||
$this->result = $result;
|
||||
if (!$this->result) {
|
||||
return $this->sqliteRaiseError(null);
|
||||
}
|
||||
|
||||
// sqlite_query() seems to allways return a resource
|
||||
// so cant use that. Using $ismanip instead
|
||||
if (!$ismanip) {
|
||||
$numRows = $this->numRows($result);
|
||||
if (is_object($numRows)) {
|
||||
// we've got PEAR_Error
|
||||
return $numRows;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal sqlite result pointer to the next available result
|
||||
*
|
||||
* @param resource $result the valid sqlite result resource
|
||||
*
|
||||
* @return bool true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum !== null) {
|
||||
if (!@sqlite_seek($this->result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
$arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
|
||||
/* Remove extraneous " characters from the fields in the result.
|
||||
* Fixes bug #11716. */
|
||||
if (is_array($arr) && count($arr) > 0) {
|
||||
$strippedArr = array();
|
||||
foreach ($arr as $field => $value) {
|
||||
$strippedArr[trim($field, '"')] = $value;
|
||||
}
|
||||
$arr = $strippedArr;
|
||||
}
|
||||
} else {
|
||||
$arr = @sqlite_fetch_array($result, SQLITE_NUM);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
/*
|
||||
* Even though this DBMS already trims output, we do this because
|
||||
* a field might have intentional whitespace at the end that
|
||||
* gets removed by DB_PORTABILITY_RTRIM under another driver.
|
||||
*/
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult(&$result)
|
||||
{
|
||||
// XXX No native free?
|
||||
if (!is_resource($result)) {
|
||||
return false;
|
||||
}
|
||||
$result = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @sqlite_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->sqliteRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$rows = @sqlite_num_rows($result);
|
||||
if ($rows === null) {
|
||||
return $this->sqliteRaiseError();
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affected()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
return @sqlite_changes($this->connection);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_sqlite::nextID(), DB_sqlite::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_sqlite::nextID(), DB_sqlite::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
$query = 'CREATE TABLE ' . $seqname .
|
||||
' (id INTEGER UNSIGNED PRIMARY KEY) ';
|
||||
$result = $this->query($query);
|
||||
if (DB::isError($result)) {
|
||||
return($result);
|
||||
}
|
||||
$query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
|
||||
BEGIN
|
||||
DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
|
||||
END ";
|
||||
$result = $this->query($query);
|
||||
if (DB::isError($result)) {
|
||||
return($result);
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_sqlite::createSequence(), DB_sqlite::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
|
||||
do {
|
||||
$repeat = 0;
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
|
||||
$this->popErrorHandling();
|
||||
if ($result === DB_OK) {
|
||||
$id = @sqlite_last_insert_rowid($this->connection);
|
||||
if ($id != 0) {
|
||||
return $id;
|
||||
}
|
||||
} elseif ($ondemand && DB::isError($result) &&
|
||||
$result->getCode() == DB_ERROR_NOSUCHTABLE)
|
||||
{
|
||||
$result = $this->createSequence($seq_name);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
} else {
|
||||
$repeat = 1;
|
||||
}
|
||||
}
|
||||
} while ($repeat);
|
||||
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getDbFileStats()
|
||||
|
||||
/**
|
||||
* Get the file stats for the current database
|
||||
*
|
||||
* Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
|
||||
* atime, mtime, ctime, blksize, blocks or a numeric key between
|
||||
* 0 and 12.
|
||||
*
|
||||
* @param string $arg the array key for stats()
|
||||
*
|
||||
* @return mixed an array on an unspecified key, integer on a passed
|
||||
* arg and false at a stats error
|
||||
*/
|
||||
function getDbFileStats($arg = '')
|
||||
{
|
||||
$stats = stat($this->dsn['database']);
|
||||
if ($stats == false) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($stats)) {
|
||||
if (is_numeric($arg)) {
|
||||
if (((int)$arg <= 12) & ((int)$arg >= 0)) {
|
||||
return false;
|
||||
}
|
||||
return $stats[$arg ];
|
||||
}
|
||||
if (array_key_exists(trim($arg), $stats)) {
|
||||
return $stats[$arg ];
|
||||
}
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ escapeSimple()
|
||||
|
||||
/**
|
||||
* Escapes a string according to the current DBMS's standards
|
||||
*
|
||||
* In SQLite, this makes things safe for inserts/updates, but may
|
||||
* cause problems when performing text comparisons against columns
|
||||
* containing binary data. See the
|
||||
* {@link http://php.net/sqlite_escape_string PHP manual} for more info.
|
||||
*
|
||||
* @param string $str the string to be escaped
|
||||
*
|
||||
* @return string the escaped string
|
||||
*
|
||||
* @since Method available since Release 1.6.1
|
||||
* @see DB_common::escapeSimple()
|
||||
*/
|
||||
function escapeSimple($str)
|
||||
{
|
||||
return @sqlite_escape_string($str);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ modifyLimitQuery()
|
||||
|
||||
/**
|
||||
* Adds LIMIT clauses to a query string according to current DBMS standards
|
||||
*
|
||||
* @param string $query the query to modify
|
||||
* @param int $from the row to start to fetching (0 = the first row)
|
||||
* @param int $count the numbers of rows to fetch
|
||||
* @param mixed $params array, string or numeric data to be used in
|
||||
* execution of the statement. Quantity of items
|
||||
* passed must match quantity of placeholders in
|
||||
* query: meaning 1 placeholder for non-array
|
||||
* parameters or 1 placeholder per array element.
|
||||
*
|
||||
* @return string the query string with LIMIT clauses added
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
function modifyLimitQuery($query, $from, $count, $params = array())
|
||||
{
|
||||
return "$query LIMIT $count OFFSET $from";
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ modifyQuery()
|
||||
|
||||
/**
|
||||
* Changes a query string for various DBMS specific reasons
|
||||
*
|
||||
* This little hack lets you know how many rows were deleted
|
||||
* when running a "DELETE FROM table" query. Only implemented
|
||||
* if the DB_PORTABILITY_DELETE_COUNT portability option is on.
|
||||
*
|
||||
* @param string $query the query string to modify
|
||||
*
|
||||
* @return string the modified query string
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::setOption()
|
||||
*/
|
||||
function modifyQuery($query)
|
||||
{
|
||||
if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
|
||||
if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
|
||||
$query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
|
||||
'DELETE FROM \1 WHERE 1=1', $query);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ sqliteRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_sqlite::errorNative(), DB_sqlite::errorCode()
|
||||
*/
|
||||
function sqliteRaiseError($errno = null)
|
||||
{
|
||||
$native = $this->errorNative();
|
||||
if ($errno === null) {
|
||||
$errno = $this->errorCode($native);
|
||||
}
|
||||
|
||||
$errorcode = @sqlite_last_error($this->connection);
|
||||
$userinfo = "$errorcode ** $this->last_query";
|
||||
|
||||
return $this->raiseError($errno, null, null, $userinfo, $native);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error message produced by the last query
|
||||
*
|
||||
* {@internal This is used to retrieve more meaningfull error messages
|
||||
* because sqlite_last_error() does not provide adequate info.}}
|
||||
*
|
||||
* @return string the DBMS' error message
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
return $this->_lasterror;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorCode()
|
||||
|
||||
/**
|
||||
* Determines PEAR::DB error code from the database's text error message
|
||||
*
|
||||
* @param string $errormsg the error message returned from the database
|
||||
*
|
||||
* @return integer the DB error number
|
||||
*/
|
||||
function errorCode($errormsg)
|
||||
{
|
||||
static $error_regexps;
|
||||
|
||||
// PHP 5.2+ prepends the function name to $php_errormsg, so we need
|
||||
// this hack to work around it, per bug #9599.
|
||||
$errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg);
|
||||
|
||||
if (!isset($error_regexps)) {
|
||||
$error_regexps = array(
|
||||
'/^no such table:/' => DB_ERROR_NOSUCHTABLE,
|
||||
'/^no such index:/' => DB_ERROR_NOT_FOUND,
|
||||
'/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
|
||||
'/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
|
||||
'/is not unique/' => DB_ERROR_CONSTRAINT,
|
||||
'/columns .* are not unique/i' => DB_ERROR_CONSTRAINT,
|
||||
'/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
|
||||
'/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'/^no such column:/' => DB_ERROR_NOSUCHFIELD,
|
||||
'/no column named/' => DB_ERROR_NOSUCHFIELD,
|
||||
'/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD,
|
||||
'/^near ".*": syntax error$/' => DB_ERROR_SYNTAX,
|
||||
'/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
);
|
||||
}
|
||||
foreach ($error_regexps as $regexp => $code) {
|
||||
if (preg_match($regexp, $errormsg)) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
// Fall back to DB_ERROR if there was no mapping.
|
||||
return DB_ERROR;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table
|
||||
*
|
||||
* @param string $result a string containing the name of a table
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
* @since Method available since Release 1.7.0
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
$id = @sqlite_array_query($this->connection,
|
||||
"PRAGMA table_info('$result');",
|
||||
SQLITE_ASSOC);
|
||||
$got_string = true;
|
||||
} else {
|
||||
$this->last_query = '';
|
||||
return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null,
|
||||
'This DBMS can not obtain tableInfo' .
|
||||
' from result sets');
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = count($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if (strpos($id[$i]['type'], '(') !== false) {
|
||||
$bits = explode('(', $id[$i]['type']);
|
||||
$type = $bits[0];
|
||||
$len = rtrim($bits[1],')');
|
||||
} else {
|
||||
$type = $id[$i]['type'];
|
||||
$len = 0;
|
||||
}
|
||||
|
||||
$flags = '';
|
||||
if ($id[$i]['pk']) {
|
||||
$flags .= 'primary_key ';
|
||||
}
|
||||
if ($id[$i]['notnull']) {
|
||||
$flags .= 'not_null ';
|
||||
}
|
||||
if ($id[$i]['dflt_value'] !== null) {
|
||||
$flags .= 'default_' . rawurlencode($id[$i]['dflt_value']);
|
||||
}
|
||||
$flags = trim($flags);
|
||||
|
||||
$res[$i] = array(
|
||||
'table' => $case_func($result),
|
||||
'name' => $case_func($id[$i]['name']),
|
||||
'type' => $type,
|
||||
'len' => $len,
|
||||
'flags' => $flags,
|
||||
);
|
||||
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
* @param array $args SQLITE DRIVER ONLY: a private array of arguments
|
||||
* used by the getSpecialQuery(). Do not use
|
||||
* this directly.
|
||||
*
|
||||
* @return string the SQL query string or null if the driver doesn't
|
||||
* support the object type requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type, $args = array())
|
||||
{
|
||||
if (!is_array($args)) {
|
||||
return $this->raiseError('no key specified', null, null, null,
|
||||
'Argument has to be an array.');
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'master':
|
||||
return 'SELECT * FROM sqlite_master;';
|
||||
case 'tables':
|
||||
return "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. 'UNION ALL SELECT name FROM sqlite_temp_master '
|
||||
. "WHERE type='table' ORDER BY name;";
|
||||
case 'schema':
|
||||
return 'SELECT sql FROM (SELECT * FROM sqlite_master '
|
||||
. 'UNION ALL SELECT * FROM sqlite_temp_master) '
|
||||
. "WHERE type!='meta' "
|
||||
. 'ORDER BY tbl_name, type DESC, name;';
|
||||
case 'schemax':
|
||||
case 'schema_x':
|
||||
/*
|
||||
* Use like:
|
||||
* $res = $db->query($db->getSpecialQuery('schema_x',
|
||||
* array('table' => 'table3')));
|
||||
*/
|
||||
return 'SELECT sql FROM (SELECT * FROM sqlite_master '
|
||||
. 'UNION ALL SELECT * FROM sqlite_temp_master) '
|
||||
. "WHERE tbl_name LIKE '{$args['table']}' "
|
||||
. "AND type!='meta' "
|
||||
. 'ORDER BY type DESC, name;';
|
||||
case 'alter':
|
||||
/*
|
||||
* SQLite does not support ALTER TABLE; this is a helper query
|
||||
* to handle this. 'table' represents the table name, 'rows'
|
||||
* the news rows to create, 'save' the row(s) to keep _with_
|
||||
* the data.
|
||||
*
|
||||
* Use like:
|
||||
* $args = array(
|
||||
* 'table' => $table,
|
||||
* 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
|
||||
* 'save' => "NULL, titel, content, datetime"
|
||||
* );
|
||||
* $res = $db->query( $db->getSpecialQuery('alter', $args));
|
||||
*/
|
||||
$rows = strtr($args['rows'], $this->keywords);
|
||||
|
||||
$q = array(
|
||||
'BEGIN TRANSACTION',
|
||||
"CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
|
||||
"INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
|
||||
"DROP TABLE {$args['table']}",
|
||||
"CREATE TABLE {$args['table']} ({$args['rows']})",
|
||||
"INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
|
||||
"DROP TABLE {$args['table']}_backup",
|
||||
'COMMIT',
|
||||
);
|
||||
|
||||
/*
|
||||
* This is a dirty hack, since the above query will not get
|
||||
* executed with a single query call so here the query method
|
||||
* will be called directly and return a select instead.
|
||||
*/
|
||||
foreach ($q as $query) {
|
||||
$this->query($query);
|
||||
}
|
||||
return "SELECT * FROM {$args['table']};";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
506
extlib/DB/storage.php
Normal file
506
extlib/DB/storage.php
Normal file
@ -0,0 +1,506 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* Provides an object interface to a table row
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Stig Bakken <stig@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: storage.php,v 1.24 2007/08/12 05:27:25 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB class so it can be extended from
|
||||
*/
|
||||
require_once 'DB.php';
|
||||
|
||||
/**
|
||||
* Provides an object interface to a table row
|
||||
*
|
||||
* It lets you add, delete and change rows using objects rather than SQL
|
||||
* statements.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Stig Bakken <stig@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_storage extends PEAR
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/** the name of the table (or view, if the backend database supports
|
||||
updates in views) we hold data from */
|
||||
var $_table = null;
|
||||
|
||||
/** which column(s) in the table contains primary keys, can be a
|
||||
string for single-column primary keys, or an array of strings
|
||||
for multiple-column primary keys */
|
||||
var $_keycolumn = null;
|
||||
|
||||
/** DB connection handle used for all transactions */
|
||||
var $_dbh = null;
|
||||
|
||||
/** an assoc with the names of database fields stored as properties
|
||||
in this object */
|
||||
var $_properties = array();
|
||||
|
||||
/** an assoc with the names of the properties in this object that
|
||||
have been changed since they were fetched from the database */
|
||||
var $_changes = array();
|
||||
|
||||
/** flag that decides if data in this object can be changed.
|
||||
objects that don't have their table's key column in their
|
||||
property lists will be flagged as read-only. */
|
||||
var $_readonly = false;
|
||||
|
||||
/** function or method that implements a validator for fields that
|
||||
are set, this validator function returns true if the field is
|
||||
valid, false if not */
|
||||
var $_validator = null;
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param $table string the name of the database table
|
||||
*
|
||||
* @param $keycolumn mixed string with name of key column, or array of
|
||||
* strings if the table has a primary key of more than one column
|
||||
*
|
||||
* @param $dbh object database connection object
|
||||
*
|
||||
* @param $validator mixed function or method used to validate
|
||||
* each new value, called with three parameters: the name of the
|
||||
* field/column that is changing, a reference to the new value and
|
||||
* a reference to this object
|
||||
*
|
||||
*/
|
||||
function DB_storage($table, $keycolumn, &$dbh, $validator = null)
|
||||
{
|
||||
$this->PEAR('DB_Error');
|
||||
$this->_table = $table;
|
||||
$this->_keycolumn = $keycolumn;
|
||||
$this->_dbh = $dbh;
|
||||
$this->_readonly = false;
|
||||
$this->_validator = $validator;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _makeWhere()
|
||||
|
||||
/**
|
||||
* Utility method to build a "WHERE" clause to locate ourselves in
|
||||
* the table.
|
||||
*
|
||||
* XXX future improvement: use rowids?
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _makeWhere($keyval = null)
|
||||
{
|
||||
if (is_array($this->_keycolumn)) {
|
||||
if ($keyval === null) {
|
||||
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
|
||||
$keyval[] = $this->{$this->_keycolumn[$i]};
|
||||
}
|
||||
}
|
||||
$whereclause = '';
|
||||
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
|
||||
if ($i > 0) {
|
||||
$whereclause .= ' AND ';
|
||||
}
|
||||
$whereclause .= $this->_keycolumn[$i];
|
||||
if (is_null($keyval[$i])) {
|
||||
// there's not much point in having a NULL key,
|
||||
// but we support it anyway
|
||||
$whereclause .= ' IS NULL';
|
||||
} else {
|
||||
$whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($keyval === null) {
|
||||
$keyval = @$this->{$this->_keycolumn};
|
||||
}
|
||||
$whereclause = $this->_keycolumn;
|
||||
if (is_null($keyval)) {
|
||||
// there's not much point in having a NULL key,
|
||||
// but we support it anyway
|
||||
$whereclause .= ' IS NULL';
|
||||
} else {
|
||||
$whereclause .= ' = ' . $this->_dbh->quote($keyval);
|
||||
}
|
||||
}
|
||||
return $whereclause;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ setup()
|
||||
|
||||
/**
|
||||
* Method used to initialize a DB_storage object from the
|
||||
* configured table.
|
||||
*
|
||||
* @param $keyval mixed the key[s] of the row to fetch (string or array)
|
||||
*
|
||||
* @return int DB_OK on success, a DB error if not
|
||||
*/
|
||||
function setup($keyval)
|
||||
{
|
||||
$whereclause = $this->_makeWhere($keyval);
|
||||
$query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
|
||||
$sth = $this->_dbh->query($query);
|
||||
if (DB::isError($sth)) {
|
||||
return $sth;
|
||||
}
|
||||
$row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
|
||||
if (DB::isError($row)) {
|
||||
return $row;
|
||||
}
|
||||
if (!$row) {
|
||||
return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
|
||||
$query, null, true);
|
||||
}
|
||||
foreach ($row as $key => $value) {
|
||||
$this->_properties[$key] = true;
|
||||
$this->$key = $value;
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ insert()
|
||||
|
||||
/**
|
||||
* Create a new (empty) row in the configured table for this
|
||||
* object.
|
||||
*/
|
||||
function insert($newpk)
|
||||
{
|
||||
if (is_array($this->_keycolumn)) {
|
||||
$primarykey = $this->_keycolumn;
|
||||
} else {
|
||||
$primarykey = array($this->_keycolumn);
|
||||
}
|
||||
settype($newpk, "array");
|
||||
for ($i = 0; $i < sizeof($primarykey); $i++) {
|
||||
$pkvals[] = $this->_dbh->quote($newpk[$i]);
|
||||
}
|
||||
|
||||
$sth = $this->_dbh->query("INSERT INTO $this->_table (" .
|
||||
implode(",", $primarykey) . ") VALUES(" .
|
||||
implode(",", $pkvals) . ")");
|
||||
if (DB::isError($sth)) {
|
||||
return $sth;
|
||||
}
|
||||
if (sizeof($newpk) == 1) {
|
||||
$newpk = $newpk[0];
|
||||
}
|
||||
$this->setup($newpk);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ toString()
|
||||
|
||||
/**
|
||||
* Output a simple description of this DB_storage object.
|
||||
* @return string object description
|
||||
*/
|
||||
function toString()
|
||||
{
|
||||
$info = strtolower(get_class($this));
|
||||
$info .= " (table=";
|
||||
$info .= $this->_table;
|
||||
$info .= ", keycolumn=";
|
||||
if (is_array($this->_keycolumn)) {
|
||||
$info .= "(" . implode(",", $this->_keycolumn) . ")";
|
||||
} else {
|
||||
$info .= $this->_keycolumn;
|
||||
}
|
||||
$info .= ", dbh=";
|
||||
if (is_object($this->_dbh)) {
|
||||
$info .= $this->_dbh->toString();
|
||||
} else {
|
||||
$info .= "null";
|
||||
}
|
||||
$info .= ")";
|
||||
if (sizeof($this->_properties)) {
|
||||
$info .= " [loaded, key=";
|
||||
$keyname = $this->_keycolumn;
|
||||
if (is_array($keyname)) {
|
||||
$info .= "(";
|
||||
for ($i = 0; $i < sizeof($keyname); $i++) {
|
||||
if ($i > 0) {
|
||||
$info .= ",";
|
||||
}
|
||||
$info .= $this->$keyname[$i];
|
||||
}
|
||||
$info .= ")";
|
||||
} else {
|
||||
$info .= $this->$keyname;
|
||||
}
|
||||
$info .= "]";
|
||||
}
|
||||
if (sizeof($this->_changes)) {
|
||||
$info .= " [modified]";
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dump()
|
||||
|
||||
/**
|
||||
* Dump the contents of this object to "standard output".
|
||||
*/
|
||||
function dump()
|
||||
{
|
||||
foreach ($this->_properties as $prop => $foo) {
|
||||
print "$prop = ";
|
||||
print htmlentities($this->$prop);
|
||||
print "<br />\n";
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ &create()
|
||||
|
||||
/**
|
||||
* Static method used to create new DB storage objects.
|
||||
* @param $data assoc. array where the keys are the names
|
||||
* of properties/columns
|
||||
* @return object a new instance of DB_storage or a subclass of it
|
||||
*/
|
||||
function &create($table, &$data)
|
||||
{
|
||||
$classname = strtolower(get_class($this));
|
||||
$obj = new $classname($table);
|
||||
foreach ($data as $name => $value) {
|
||||
$obj->_properties[$name] = true;
|
||||
$obj->$name = &$value;
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ loadFromQuery()
|
||||
|
||||
/**
|
||||
* Loads data into this object from the given query. If this
|
||||
* object already contains table data, changes will be saved and
|
||||
* the object re-initialized first.
|
||||
*
|
||||
* @param $query SQL query
|
||||
*
|
||||
* @param $params parameter list in case you want to use
|
||||
* prepare/execute mode
|
||||
*
|
||||
* @return int DB_OK on success, DB_WARNING_READ_ONLY if the
|
||||
* returned object is read-only (because the object's specified
|
||||
* key column was not found among the columns returned by $query),
|
||||
* or another DB error code in case of errors.
|
||||
*/
|
||||
// XXX commented out for now
|
||||
/*
|
||||
function loadFromQuery($query, $params = null)
|
||||
{
|
||||
if (sizeof($this->_properties)) {
|
||||
if (sizeof($this->_changes)) {
|
||||
$this->store();
|
||||
$this->_changes = array();
|
||||
}
|
||||
$this->_properties = array();
|
||||
}
|
||||
$rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
|
||||
if (DB::isError($rowdata)) {
|
||||
return $rowdata;
|
||||
}
|
||||
reset($rowdata);
|
||||
$found_keycolumn = false;
|
||||
while (list($key, $value) = each($rowdata)) {
|
||||
if ($key == $this->_keycolumn) {
|
||||
$found_keycolumn = true;
|
||||
}
|
||||
$this->_properties[$key] = true;
|
||||
$this->$key = &$value;
|
||||
unset($value); // have to unset, or all properties will
|
||||
// refer to the same value
|
||||
}
|
||||
if (!$found_keycolumn) {
|
||||
$this->_readonly = true;
|
||||
return DB_WARNING_READ_ONLY;
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
// }}}
|
||||
// {{{ set()
|
||||
|
||||
/**
|
||||
* Modify an attriute value.
|
||||
*/
|
||||
function set($property, $newvalue)
|
||||
{
|
||||
// only change if $property is known and object is not
|
||||
// read-only
|
||||
if ($this->_readonly) {
|
||||
return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
|
||||
null, null, null, true);
|
||||
}
|
||||
if (@isset($this->_properties[$property])) {
|
||||
if (empty($this->_validator)) {
|
||||
$valid = true;
|
||||
} else {
|
||||
$valid = @call_user_func($this->_validator,
|
||||
$this->_table,
|
||||
$property,
|
||||
$newvalue,
|
||||
$this->$property,
|
||||
$this);
|
||||
}
|
||||
if ($valid) {
|
||||
$this->$property = $newvalue;
|
||||
if (empty($this->_changes[$property])) {
|
||||
$this->_changes[$property] = 0;
|
||||
} else {
|
||||
$this->_changes[$property]++;
|
||||
}
|
||||
} else {
|
||||
return $this->raiseError(null, DB_ERROR_INVALID, null,
|
||||
null, "invalid field: $property",
|
||||
null, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
|
||||
null, "unknown field: $property",
|
||||
null, true);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ &get()
|
||||
|
||||
/**
|
||||
* Fetch an attribute value.
|
||||
*
|
||||
* @param string attribute name
|
||||
*
|
||||
* @return attribute contents, or null if the attribute name is
|
||||
* unknown
|
||||
*/
|
||||
function &get($property)
|
||||
{
|
||||
// only return if $property is known
|
||||
if (isset($this->_properties[$property])) {
|
||||
return $this->$property;
|
||||
}
|
||||
$tmp = null;
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _DB_storage()
|
||||
|
||||
/**
|
||||
* Destructor, calls DB_storage::store() if there are changes
|
||||
* that are to be kept.
|
||||
*/
|
||||
function _DB_storage()
|
||||
{
|
||||
if (sizeof($this->_changes)) {
|
||||
$this->store();
|
||||
}
|
||||
$this->_properties = array();
|
||||
$this->_changes = array();
|
||||
$this->_table = null;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ store()
|
||||
|
||||
/**
|
||||
* Stores changes to this object in the database.
|
||||
*
|
||||
* @return DB_OK or a DB error
|
||||
*/
|
||||
function store()
|
||||
{
|
||||
$params = array();
|
||||
$vars = array();
|
||||
foreach ($this->_changes as $name => $foo) {
|
||||
$params[] = &$this->$name;
|
||||
$vars[] = $name . ' = ?';
|
||||
}
|
||||
if ($vars) {
|
||||
$query = 'UPDATE ' . $this->_table . ' SET ' .
|
||||
implode(', ', $vars) . ' WHERE ' .
|
||||
$this->_makeWhere();
|
||||
$stmt = $this->_dbh->prepare($query);
|
||||
$res = $this->_dbh->execute($stmt, $params);
|
||||
if (DB::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$this->_changes = array();
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ remove()
|
||||
|
||||
/**
|
||||
* Remove the row represented by this object from the database.
|
||||
*
|
||||
* @return mixed DB_OK or a DB error
|
||||
*/
|
||||
function remove()
|
||||
{
|
||||
if ($this->_readonly) {
|
||||
return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
|
||||
null, null, null, true);
|
||||
}
|
||||
$query = 'DELETE FROM ' . $this->_table .' WHERE '.
|
||||
$this->_makeWhere();
|
||||
$res = $this->_dbh->query($query);
|
||||
if (DB::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
foreach ($this->_properties as $prop => $foo) {
|
||||
unset($this->$prop);
|
||||
}
|
||||
$this->_properties = array();
|
||||
$this->_changes = array();
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
942
extlib/DB/sybase.php
Normal file
942
extlib/DB/sybase.php
Normal file
@ -0,0 +1,942 @@
|
||||
<?php
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* The PEAR DB driver for PHP's sybase extension
|
||||
* for interacting with Sybase databases
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Sterling Hughes <sterling@php.net>
|
||||
* @author Antônio Carlos Venâncio Júnior <floripa@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: sybase.php,v 1.87 2007/09/21 13:40:42 aharvey Exp $
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the DB_common class so it can be extended from
|
||||
*/
|
||||
require_once 'DB/common.php';
|
||||
|
||||
/**
|
||||
* The methods PEAR DB uses to interact with PHP's sybase extension
|
||||
* for interacting with Sybase databases
|
||||
*
|
||||
* These methods overload the ones declared in DB_common.
|
||||
*
|
||||
* WARNING: This driver may fail with multiple connections under the
|
||||
* same user/pass/host and different databases.
|
||||
*
|
||||
* @category Database
|
||||
* @package DB
|
||||
* @author Sterling Hughes <sterling@php.net>
|
||||
* @author Antônio Carlos Venâncio Júnior <floripa@php.net>
|
||||
* @author Daniel Convissor <danielc@php.net>
|
||||
* @copyright 1997-2007 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version Release: 1.7.14RC1
|
||||
* @link http://pear.php.net/package/DB
|
||||
*/
|
||||
class DB_sybase extends DB_common
|
||||
{
|
||||
// {{{ properties
|
||||
|
||||
/**
|
||||
* The DB driver type (mysql, oci8, odbc, etc.)
|
||||
* @var string
|
||||
*/
|
||||
var $phptype = 'sybase';
|
||||
|
||||
/**
|
||||
* The database syntax variant to be used (db2, access, etc.), if any
|
||||
* @var string
|
||||
*/
|
||||
var $dbsyntax = 'sybase';
|
||||
|
||||
/**
|
||||
* The capabilities of this DB implementation
|
||||
*
|
||||
* The 'new_link' element contains the PHP version that first provided
|
||||
* new_link support for this DBMS. Contains false if it's unsupported.
|
||||
*
|
||||
* Meaning of the 'limit' element:
|
||||
* + 'emulate' = emulate with fetch row by number
|
||||
* + 'alter' = alter the query
|
||||
* + false = skip rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $features = array(
|
||||
'limit' => 'emulate',
|
||||
'new_link' => false,
|
||||
'numrows' => true,
|
||||
'pconnect' => true,
|
||||
'prepare' => false,
|
||||
'ssl' => false,
|
||||
'transactions' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of native error codes to DB error codes
|
||||
* @var array
|
||||
*/
|
||||
var $errorcode_map = array(
|
||||
);
|
||||
|
||||
/**
|
||||
* The raw database connection created by PHP
|
||||
* @var resource
|
||||
*/
|
||||
var $connection;
|
||||
|
||||
/**
|
||||
* The DSN information for connecting to a database
|
||||
* @var array
|
||||
*/
|
||||
var $dsn = array();
|
||||
|
||||
|
||||
/**
|
||||
* Should data manipulation queries be committed automatically?
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
var $autocommit = true;
|
||||
|
||||
/**
|
||||
* The quantity of transactions begun
|
||||
*
|
||||
* {@internal While this is private, it can't actually be designated
|
||||
* private in PHP 5 because it is directly accessed in the test suite.}}
|
||||
*
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
var $transaction_opcount = 0;
|
||||
|
||||
/**
|
||||
* The database specified in the DSN
|
||||
*
|
||||
* It's a fix to allow calls to different databases in the same script.
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
var $_db = '';
|
||||
|
||||
|
||||
// }}}
|
||||
// {{{ constructor
|
||||
|
||||
/**
|
||||
* This constructor calls <kbd>$this->DB_common()</kbd>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function DB_sybase()
|
||||
{
|
||||
$this->DB_common();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ connect()
|
||||
|
||||
/**
|
||||
* Connect to the database server, log in and open the database
|
||||
*
|
||||
* Don't call this method directly. Use DB::connect() instead.
|
||||
*
|
||||
* PEAR DB's sybase driver supports the following extra DSN options:
|
||||
* + appname The application name to use on this connection.
|
||||
* Available since PEAR DB 1.7.0.
|
||||
* + charset The character set to use on this connection.
|
||||
* Available since PEAR DB 1.7.0.
|
||||
*
|
||||
* @param array $dsn the data source name
|
||||
* @param bool $persistent should the connection be persistent?
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function connect($dsn, $persistent = false)
|
||||
{
|
||||
if (!PEAR::loadExtension('sybase') &&
|
||||
!PEAR::loadExtension('sybase_ct'))
|
||||
{
|
||||
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->dsn = $dsn;
|
||||
if ($dsn['dbsyntax']) {
|
||||
$this->dbsyntax = $dsn['dbsyntax'];
|
||||
}
|
||||
|
||||
$dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost';
|
||||
$dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false;
|
||||
$dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false;
|
||||
$dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false;
|
||||
|
||||
$connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
|
||||
|
||||
if ($dsn['username']) {
|
||||
$this->connection = @$connect_function($dsn['hostspec'],
|
||||
$dsn['username'],
|
||||
$dsn['password'],
|
||||
$dsn['charset'],
|
||||
$dsn['appname']);
|
||||
} else {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
'The DSN did not contain a username.');
|
||||
}
|
||||
|
||||
if (!$this->connection) {
|
||||
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
|
||||
null, null, null,
|
||||
@sybase_get_last_message());
|
||||
}
|
||||
|
||||
if ($dsn['database']) {
|
||||
if (!@sybase_select_db($dsn['database'], $this->connection)) {
|
||||
return $this->raiseError(DB_ERROR_NODBSELECTED,
|
||||
null, null, null,
|
||||
@sybase_get_last_message());
|
||||
}
|
||||
$this->_db = $dsn['database'];
|
||||
}
|
||||
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ disconnect()
|
||||
|
||||
/**
|
||||
* Disconnects from the database server
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on failure
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
$ret = @sybase_close($this->connection);
|
||||
$this->connection = null;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ simpleQuery()
|
||||
|
||||
/**
|
||||
* Sends a query to the database server
|
||||
*
|
||||
* @param string the SQL query string
|
||||
*
|
||||
* @return mixed + a PHP result resrouce for successful SELECT queries
|
||||
* + the DB_OK constant for other successful queries
|
||||
* + a DB_Error object on failure
|
||||
*/
|
||||
function simpleQuery($query)
|
||||
{
|
||||
$ismanip = $this->_checkManip($query);
|
||||
$this->last_query = $query;
|
||||
if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$query = $this->modifyQuery($query);
|
||||
if (!$this->autocommit && $ismanip) {
|
||||
if ($this->transaction_opcount == 0) {
|
||||
$result = @sybase_query('BEGIN TRANSACTION', $this->connection);
|
||||
if (!$result) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
}
|
||||
$this->transaction_opcount++;
|
||||
}
|
||||
$result = @sybase_query($query, $this->connection);
|
||||
if (!$result) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
if (is_resource($result)) {
|
||||
return $result;
|
||||
}
|
||||
// Determine which queries that should return data, and which
|
||||
// should return an error code only.
|
||||
return $ismanip ? DB_OK : $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextResult()
|
||||
|
||||
/**
|
||||
* Move the internal sybase result pointer to the next available result
|
||||
*
|
||||
* @param a valid sybase result resource
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return true if a result is available otherwise return false
|
||||
*/
|
||||
function nextResult($result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ fetchInto()
|
||||
|
||||
/**
|
||||
* Places a row from the result set into the given array
|
||||
*
|
||||
* Formating of the array and the data therein are configurable.
|
||||
* See DB_result::fetchInto() for more information.
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::fetchInto() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result the query result resource
|
||||
* @param array $arr the referenced array to put the data in
|
||||
* @param int $fetchmode how the resulting array should be indexed
|
||||
* @param int $rownum the row number to fetch (0 = first row)
|
||||
*
|
||||
* @return mixed DB_OK on success, NULL when the end of a result set is
|
||||
* reached or on failure
|
||||
*
|
||||
* @see DB_result::fetchInto()
|
||||
*/
|
||||
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
|
||||
{
|
||||
if ($rownum !== null) {
|
||||
if (!@sybase_data_seek($result, $rownum)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($fetchmode & DB_FETCHMODE_ASSOC) {
|
||||
if (function_exists('sybase_fetch_assoc')) {
|
||||
$arr = @sybase_fetch_assoc($result);
|
||||
} else {
|
||||
if ($arr = @sybase_fetch_array($result)) {
|
||||
foreach ($arr as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
unset($arr[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
|
||||
$arr = array_change_key_case($arr, CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
$arr = @sybase_fetch_row($result);
|
||||
}
|
||||
if (!$arr) {
|
||||
return null;
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
|
||||
$this->_rtrimArrayValues($arr);
|
||||
}
|
||||
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
|
||||
$this->_convertNullArrayValuesToEmpty($arr);
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ freeResult()
|
||||
|
||||
/**
|
||||
* Deletes the result set and frees the memory occupied by the result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::free() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if $result is invalid
|
||||
*
|
||||
* @see DB_result::free()
|
||||
*/
|
||||
function freeResult($result)
|
||||
{
|
||||
return is_resource($result) ? sybase_free_result($result) : false;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numCols()
|
||||
|
||||
/**
|
||||
* Gets the number of columns in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numCols() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of columns. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numCols()
|
||||
*/
|
||||
function numCols($result)
|
||||
{
|
||||
$cols = @sybase_num_fields($result);
|
||||
if (!$cols) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
return $cols;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ numRows()
|
||||
|
||||
/**
|
||||
* Gets the number of rows in a result set
|
||||
*
|
||||
* This method is not meant to be called directly. Use
|
||||
* DB_result::numRows() instead. It can't be declared "protected"
|
||||
* because DB_result is a separate object.
|
||||
*
|
||||
* @param resource $result PHP's query result resource
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_result::numRows()
|
||||
*/
|
||||
function numRows($result)
|
||||
{
|
||||
$rows = @sybase_num_rows($result);
|
||||
if ($rows === false) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ affectedRows()
|
||||
|
||||
/**
|
||||
* Determines the number of rows affected by a data maniuplation query
|
||||
*
|
||||
* 0 is returned for queries that don't manipulate data.
|
||||
*
|
||||
* @return int the number of rows. A DB_Error object on failure.
|
||||
*/
|
||||
function affectedRows()
|
||||
{
|
||||
if ($this->_last_query_manip) {
|
||||
$result = @sybase_affected_rows($this->connection);
|
||||
} else {
|
||||
$result = 0;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ nextId()
|
||||
|
||||
/**
|
||||
* Returns the next free id in a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence
|
||||
* @param boolean $ondemand when true, the seqence is automatically
|
||||
* created if it does not exist
|
||||
*
|
||||
* @return int the next id number in the sequence.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::nextID(), DB_common::getSequenceName(),
|
||||
* DB_sybase::createSequence(), DB_sybase::dropSequence()
|
||||
*/
|
||||
function nextId($seq_name, $ondemand = true)
|
||||
{
|
||||
$seqname = $this->getSequenceName($seq_name);
|
||||
if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$repeat = 0;
|
||||
do {
|
||||
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
|
||||
$this->popErrorHandling();
|
||||
if ($ondemand && DB::isError($result) &&
|
||||
($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
|
||||
{
|
||||
$repeat = 1;
|
||||
$result = $this->createSequence($seq_name);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
} elseif (!DB::isError($result)) {
|
||||
$result = $this->query("SELECT @@IDENTITY FROM $seqname");
|
||||
$repeat = 0;
|
||||
} else {
|
||||
$repeat = false;
|
||||
}
|
||||
} while ($repeat);
|
||||
if (DB::isError($result)) {
|
||||
return $this->raiseError($result);
|
||||
}
|
||||
$result = $result->fetchRow(DB_FETCHMODE_ORDERED);
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence
|
||||
*
|
||||
* @param string $seq_name name of the new sequence
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::createSequence(), DB_common::getSequenceName(),
|
||||
* DB_sybase::nextID(), DB_sybase::dropSequence()
|
||||
*/
|
||||
function createSequence($seq_name)
|
||||
{
|
||||
return $this->query('CREATE TABLE '
|
||||
. $this->getSequenceName($seq_name)
|
||||
. ' (id numeric(10, 0) IDENTITY NOT NULL,'
|
||||
. ' vapor int NULL)');
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ dropSequence()
|
||||
|
||||
/**
|
||||
* Deletes a sequence
|
||||
*
|
||||
* @param string $seq_name name of the sequence to be deleted
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
|
||||
* DB_sybase::nextID(), DB_sybase::createSequence()
|
||||
*/
|
||||
function dropSequence($seq_name)
|
||||
{
|
||||
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ quoteFloat()
|
||||
|
||||
/**
|
||||
* Formats a float value for use within a query in a locale-independent
|
||||
* manner.
|
||||
*
|
||||
* @param float the float value to be quoted.
|
||||
* @return string the quoted string.
|
||||
* @see DB_common::quoteSmart()
|
||||
* @since Method available since release 1.7.8.
|
||||
*/
|
||||
function quoteFloat($float) {
|
||||
return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ autoCommit()
|
||||
|
||||
/**
|
||||
* Enables or disables automatic commits
|
||||
*
|
||||
* @param bool $onoff true turns it on, false turns it off
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object if the driver
|
||||
* doesn't support auto-committing transactions.
|
||||
*/
|
||||
function autoCommit($onoff = false)
|
||||
{
|
||||
// XXX if $this->transaction_opcount > 0, we should probably
|
||||
// issue a warning here.
|
||||
$this->autocommit = $onoff ? true : false;
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ commit()
|
||||
|
||||
/**
|
||||
* Commits the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function commit()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$result = @sybase_query('COMMIT', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ rollback()
|
||||
|
||||
/**
|
||||
* Reverts the current transaction
|
||||
*
|
||||
* @return int DB_OK on success. A DB_Error object on failure.
|
||||
*/
|
||||
function rollback()
|
||||
{
|
||||
if ($this->transaction_opcount > 0) {
|
||||
if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$result = @sybase_query('ROLLBACK', $this->connection);
|
||||
$this->transaction_opcount = 0;
|
||||
if (!$result) {
|
||||
return $this->sybaseRaiseError();
|
||||
}
|
||||
}
|
||||
return DB_OK;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ sybaseRaiseError()
|
||||
|
||||
/**
|
||||
* Produces a DB_Error object regarding the current problem
|
||||
*
|
||||
* @param int $errno if the error is being manually raised pass a
|
||||
* DB_ERROR* constant here. If this isn't passed
|
||||
* the error information gathered from the DBMS.
|
||||
*
|
||||
* @return object the DB_Error object
|
||||
*
|
||||
* @see DB_common::raiseError(),
|
||||
* DB_sybase::errorNative(), DB_sybase::errorCode()
|
||||
*/
|
||||
function sybaseRaiseError($errno = null)
|
||||
{
|
||||
$native = $this->errorNative();
|
||||
if ($errno === null) {
|
||||
$errno = $this->errorCode($native);
|
||||
}
|
||||
return $this->raiseError($errno, null, null, null, $native);
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorNative()
|
||||
|
||||
/**
|
||||
* Gets the DBMS' native error message produced by the last query
|
||||
*
|
||||
* @return string the DBMS' error message
|
||||
*/
|
||||
function errorNative()
|
||||
{
|
||||
return @sybase_get_last_message();
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ errorCode()
|
||||
|
||||
/**
|
||||
* Determines PEAR::DB error code from the database's text error message.
|
||||
*
|
||||
* @param string $errormsg error message returned from the database
|
||||
* @return integer an error number from a DB error constant
|
||||
*/
|
||||
function errorCode($errormsg)
|
||||
{
|
||||
static $error_regexps;
|
||||
|
||||
// PHP 5.2+ prepends the function name to $php_errormsg, so we need
|
||||
// this hack to work around it, per bug #9599.
|
||||
$errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg);
|
||||
|
||||
if (!isset($error_regexps)) {
|
||||
$error_regexps = array(
|
||||
'/Incorrect syntax near/'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/^Unclosed quote before the character string [\"\'].*[\"\']\./'
|
||||
=> DB_ERROR_SYNTAX,
|
||||
'/Implicit conversion (from datatype|of NUMERIC value)/i'
|
||||
=> DB_ERROR_INVALID_NUMBER,
|
||||
'/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
|
||||
=> DB_ERROR_NOSUCHTABLE,
|
||||
'/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
|
||||
=> DB_ERROR_ACCESS_VIOLATION,
|
||||
'/^.+ permission denied on object .+, database .+, owner .+/'
|
||||
=> DB_ERROR_ACCESS_VIOLATION,
|
||||
'/^.* permission denied, database .+, owner .+/'
|
||||
=> DB_ERROR_ACCESS_VIOLATION,
|
||||
'/[^.*] not found\./'
|
||||
=> DB_ERROR_NOSUCHTABLE,
|
||||
'/There is already an object named/'
|
||||
=> DB_ERROR_ALREADY_EXISTS,
|
||||
'/Invalid column name/'
|
||||
=> DB_ERROR_NOSUCHFIELD,
|
||||
'/does not allow null values/'
|
||||
=> DB_ERROR_CONSTRAINT_NOT_NULL,
|
||||
'/Command has been aborted/'
|
||||
=> DB_ERROR_CONSTRAINT,
|
||||
'/^Cannot drop the index .* because it doesn\'t exist/i'
|
||||
=> DB_ERROR_NOT_FOUND,
|
||||
'/^There is already an index/i'
|
||||
=> DB_ERROR_ALREADY_EXISTS,
|
||||
'/^There are fewer columns in the INSERT statement than values specified/i'
|
||||
=> DB_ERROR_VALUE_COUNT_ON_ROW,
|
||||
'/Divide by zero/i'
|
||||
=> DB_ERROR_DIVZERO,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($error_regexps as $regexp => $code) {
|
||||
if (preg_match($regexp, $errormsg)) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
return DB_ERROR;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ tableInfo()
|
||||
|
||||
/**
|
||||
* Returns information about a table or a result set
|
||||
*
|
||||
* NOTE: only supports 'table' and 'flags' if <var>$result</var>
|
||||
* is a table name.
|
||||
*
|
||||
* @param object|string $result DB_result object from a query or a
|
||||
* string containing the name of a table.
|
||||
* While this also accepts a query result
|
||||
* resource identifier, this behavior is
|
||||
* deprecated.
|
||||
* @param int $mode a valid tableInfo mode
|
||||
*
|
||||
* @return array an associative array with the information requested.
|
||||
* A DB_Error object on failure.
|
||||
*
|
||||
* @see DB_common::tableInfo()
|
||||
* @since Method available since Release 1.6.0
|
||||
*/
|
||||
function tableInfo($result, $mode = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
/*
|
||||
* Probably received a table name.
|
||||
* Create a result resource identifier.
|
||||
*/
|
||||
if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
|
||||
}
|
||||
$id = @sybase_query("SELECT * FROM $result WHERE 1=0",
|
||||
$this->connection);
|
||||
$got_string = true;
|
||||
} elseif (isset($result->result)) {
|
||||
/*
|
||||
* Probably received a result object.
|
||||
* Extract the result resource identifier.
|
||||
*/
|
||||
$id = $result->result;
|
||||
$got_string = false;
|
||||
} else {
|
||||
/*
|
||||
* Probably received a result resource identifier.
|
||||
* Copy it.
|
||||
* Deprecated. Here for compatibility only.
|
||||
*/
|
||||
$id = $result;
|
||||
$got_string = false;
|
||||
}
|
||||
|
||||
if (!is_resource($id)) {
|
||||
return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
|
||||
$case_func = 'strtolower';
|
||||
} else {
|
||||
$case_func = 'strval';
|
||||
}
|
||||
|
||||
$count = @sybase_num_fields($id);
|
||||
$res = array();
|
||||
|
||||
if ($mode) {
|
||||
$res['num_fields'] = $count;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$f = @sybase_fetch_field($id, $i);
|
||||
// column_source is often blank
|
||||
$res[$i] = array(
|
||||
'table' => $got_string
|
||||
? $case_func($result)
|
||||
: $case_func($f->column_source),
|
||||
'name' => $case_func($f->name),
|
||||
'type' => $f->type,
|
||||
'len' => $f->max_length,
|
||||
'flags' => '',
|
||||
);
|
||||
if ($res[$i]['table']) {
|
||||
$res[$i]['flags'] = $this->_sybase_field_flags(
|
||||
$res[$i]['table'], $res[$i]['name']);
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDER) {
|
||||
$res['order'][$res[$i]['name']] = $i;
|
||||
}
|
||||
if ($mode & DB_TABLEINFO_ORDERTABLE) {
|
||||
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// free the result only if we were called on a table
|
||||
if ($got_string) {
|
||||
@sybase_free_result($id);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _sybase_field_flags()
|
||||
|
||||
/**
|
||||
* Get the flags for a field
|
||||
*
|
||||
* Currently supports:
|
||||
* + <samp>unique_key</samp> (unique index, unique check or primary_key)
|
||||
* + <samp>multiple_key</samp> (multi-key index)
|
||||
*
|
||||
* @param string $table the table name
|
||||
* @param string $column the field name
|
||||
*
|
||||
* @return string space delimited string of flags. Empty string if none.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _sybase_field_flags($table, $column)
|
||||
{
|
||||
static $tableName = null;
|
||||
static $flags = array();
|
||||
|
||||
if ($table != $tableName) {
|
||||
$flags = array();
|
||||
$tableName = $table;
|
||||
|
||||
/* We're running sp_helpindex directly because it doesn't exist in
|
||||
* older versions of ASE -- unfortunately, we can't just use
|
||||
* DB::isError() because the user may be using callback error
|
||||
* handling. */
|
||||
$res = @sybase_query("sp_helpindex $table", $this->connection);
|
||||
|
||||
if ($res === false || $res === true) {
|
||||
// Fake a valid response for BC reasons.
|
||||
return '';
|
||||
}
|
||||
|
||||
while (($val = sybase_fetch_assoc($res)) !== false) {
|
||||
if (!isset($val['index_keys'])) {
|
||||
/* No useful information returned. Break and be done with
|
||||
* it, which preserves the pre-1.7.9 behaviour. */
|
||||
break;
|
||||
}
|
||||
|
||||
$keys = explode(', ', trim($val['index_keys']));
|
||||
|
||||
if (sizeof($keys) > 1) {
|
||||
foreach ($keys as $key) {
|
||||
$this->_add_flag($flags[$key], 'multiple_key');
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($val['index_description'], 'unique')) {
|
||||
foreach ($keys as $key) {
|
||||
$this->_add_flag($flags[$key], 'unique_key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sybase_free_result($res);
|
||||
|
||||
}
|
||||
|
||||
if (array_key_exists($column, $flags)) {
|
||||
return(implode(' ', $flags[$column]));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ _add_flag()
|
||||
|
||||
/**
|
||||
* Adds a string to the flags array if the flag is not yet in there
|
||||
* - if there is no flag present the array is created
|
||||
*
|
||||
* @param array $array reference of flags array to add a value to
|
||||
* @param mixed $value value to add to the flag array
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function _add_flag(&$array, $value)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
$array = array($value);
|
||||
} elseif (!in_array($value, $array)) {
|
||||
array_push($array, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
// {{{ getSpecialQuery()
|
||||
|
||||
/**
|
||||
* Obtains the query string needed for listing a given type of objects
|
||||
*
|
||||
* @param string $type the kind of objects you want to retrieve
|
||||
*
|
||||
* @return string the SQL query string or null if the driver doesn't
|
||||
* support the object type requested
|
||||
*
|
||||
* @access protected
|
||||
* @see DB_common::getListOf()
|
||||
*/
|
||||
function getSpecialQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'tables':
|
||||
return "SELECT name FROM sysobjects WHERE type = 'U'"
|
||||
. ' ORDER BY name';
|
||||
case 'views':
|
||||
return "SELECT name FROM sysobjects WHERE type = 'V'";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*/
|
||||
|
||||
?>
|
940
extlib/Mail/RFC822.php
Normal file
940
extlib/Mail/RFC822.php
Normal file
@ -0,0 +1,940 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | Copyright (c) 2001-2002, Richard Heyes |
|
||||
// | All rights reserved. |
|
||||
// | |
|
||||
// | Redistribution and use in source and binary forms, with or without |
|
||||
// | modification, are permitted provided that the following conditions |
|
||||
// | are met: |
|
||||
// | |
|
||||
// | o Redistributions of source code must retain the above copyright |
|
||||
// | notice, this list of conditions and the following disclaimer. |
|
||||
// | o Redistributions in binary form must reproduce the above copyright |
|
||||
// | notice, this list of conditions and the following disclaimer in the |
|
||||
// | documentation and/or other materials provided with the distribution.|
|
||||
// | o The names of the authors may not be used to endorse or promote |
|
||||
// | products derived from this software without specific prior written |
|
||||
// | permission. |
|
||||
// | |
|
||||
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||
// | |
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | Authors: Richard Heyes <richard@phpguru.org> |
|
||||
// | Chuck Hagenbuch <chuck@horde.org> |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* RFC 822 Email address list validation Utility
|
||||
*
|
||||
* What is it?
|
||||
*
|
||||
* This class will take an address string, and parse it into it's consituent
|
||||
* parts, be that either addresses, groups, or combinations. Nested groups
|
||||
* are not supported. The structure it returns is pretty straight forward,
|
||||
* and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
|
||||
* print_r() to view the structure.
|
||||
*
|
||||
* How do I use it?
|
||||
*
|
||||
* $address_string = 'My Group: "Richard" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
|
||||
* $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
|
||||
* print_r($structure);
|
||||
*
|
||||
* @author Richard Heyes <richard@phpguru.org>
|
||||
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||
* @version $Revision: 1.24 $
|
||||
* @license BSD
|
||||
* @package Mail
|
||||
*/
|
||||
class Mail_RFC822 {
|
||||
|
||||
/**
|
||||
* The address being parsed by the RFC822 object.
|
||||
* @var string $address
|
||||
*/
|
||||
var $address = '';
|
||||
|
||||
/**
|
||||
* The default domain to use for unqualified addresses.
|
||||
* @var string $default_domain
|
||||
*/
|
||||
var $default_domain = 'localhost';
|
||||
|
||||
/**
|
||||
* Should we return a nested array showing groups, or flatten everything?
|
||||
* @var boolean $nestGroups
|
||||
*/
|
||||
var $nestGroups = true;
|
||||
|
||||
/**
|
||||
* Whether or not to validate atoms for non-ascii characters.
|
||||
* @var boolean $validate
|
||||
*/
|
||||
var $validate = true;
|
||||
|
||||
/**
|
||||
* The array of raw addresses built up as we parse.
|
||||
* @var array $addresses
|
||||
*/
|
||||
var $addresses = array();
|
||||
|
||||
/**
|
||||
* The final array of parsed address information that we build up.
|
||||
* @var array $structure
|
||||
*/
|
||||
var $structure = array();
|
||||
|
||||
/**
|
||||
* The current error message, if any.
|
||||
* @var string $error
|
||||
*/
|
||||
var $error = null;
|
||||
|
||||
/**
|
||||
* An internal counter/pointer.
|
||||
* @var integer $index
|
||||
*/
|
||||
var $index = null;
|
||||
|
||||
/**
|
||||
* The number of groups that have been found in the address list.
|
||||
* @var integer $num_groups
|
||||
* @access public
|
||||
*/
|
||||
var $num_groups = 0;
|
||||
|
||||
/**
|
||||
* A variable so that we can tell whether or not we're inside a
|
||||
* Mail_RFC822 object.
|
||||
* @var boolean $mailRFC822
|
||||
*/
|
||||
var $mailRFC822 = true;
|
||||
|
||||
/**
|
||||
* A limit after which processing stops
|
||||
* @var int $limit
|
||||
*/
|
||||
var $limit = null;
|
||||
|
||||
/**
|
||||
* Sets up the object. The address must either be set here or when
|
||||
* calling parseAddressList(). One or the other.
|
||||
*
|
||||
* @access public
|
||||
* @param string $address The address(es) to validate.
|
||||
* @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
|
||||
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
|
||||
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
|
||||
*
|
||||
* @return object Mail_RFC822 A new Mail_RFC822 object.
|
||||
*/
|
||||
function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
|
||||
{
|
||||
if (isset($address)) $this->address = $address;
|
||||
if (isset($default_domain)) $this->default_domain = $default_domain;
|
||||
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
|
||||
if (isset($validate)) $this->validate = $validate;
|
||||
if (isset($limit)) $this->limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the whole process. The address must either be set here
|
||||
* or when creating the object. One or the other.
|
||||
*
|
||||
* @access public
|
||||
* @param string $address The address(es) to validate.
|
||||
* @param string $default_domain Default domain/host etc.
|
||||
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
|
||||
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
|
||||
*
|
||||
* @return array A structured array of addresses.
|
||||
*/
|
||||
function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
|
||||
{
|
||||
if (!isset($this) || !isset($this->mailRFC822)) {
|
||||
$obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
|
||||
return $obj->parseAddressList();
|
||||
}
|
||||
|
||||
if (isset($address)) $this->address = $address;
|
||||
if (isset($default_domain)) $this->default_domain = $default_domain;
|
||||
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
|
||||
if (isset($validate)) $this->validate = $validate;
|
||||
if (isset($limit)) $this->limit = $limit;
|
||||
|
||||
$this->structure = array();
|
||||
$this->addresses = array();
|
||||
$this->error = null;
|
||||
$this->index = null;
|
||||
|
||||
// Unfold any long lines in $this->address.
|
||||
$this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
|
||||
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
|
||||
|
||||
while ($this->address = $this->_splitAddresses($this->address));
|
||||
|
||||
if ($this->address === false || isset($this->error)) {
|
||||
require_once 'PEAR.php';
|
||||
return PEAR::raiseError($this->error);
|
||||
}
|
||||
|
||||
// Validate each address individually. If we encounter an invalid
|
||||
// address, stop iterating and return an error immediately.
|
||||
foreach ($this->addresses as $address) {
|
||||
$valid = $this->_validateAddress($address);
|
||||
|
||||
if ($valid === false || isset($this->error)) {
|
||||
require_once 'PEAR.php';
|
||||
return PEAR::raiseError($this->error);
|
||||
}
|
||||
|
||||
if (!$this->nestGroups) {
|
||||
$this->structure = array_merge($this->structure, $valid);
|
||||
} else {
|
||||
$this->structure[] = $valid;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an address into separate addresses.
|
||||
*
|
||||
* @access private
|
||||
* @param string $address The addresses to split.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _splitAddresses($address)
|
||||
{
|
||||
if (!empty($this->limit) && count($this->addresses) == $this->limit) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->_isGroup($address) && !isset($this->error)) {
|
||||
$split_char = ';';
|
||||
$is_group = true;
|
||||
} elseif (!isset($this->error)) {
|
||||
$split_char = ',';
|
||||
$is_group = false;
|
||||
} elseif (isset($this->error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split the string based on the above ten or so lines.
|
||||
$parts = explode($split_char, $address);
|
||||
$string = $this->_splitCheck($parts, $split_char);
|
||||
|
||||
// If a group...
|
||||
if ($is_group) {
|
||||
// If $string does not contain a colon outside of
|
||||
// brackets/quotes etc then something's fubar.
|
||||
|
||||
// First check there's a colon at all:
|
||||
if (strpos($string, ':') === false) {
|
||||
$this->error = 'Invalid address: ' . $string;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now check it's outside of brackets/quotes:
|
||||
if (!$this->_splitCheck(explode(':', $string), ':')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We must have a group at this point, so increase the counter:
|
||||
$this->num_groups++;
|
||||
}
|
||||
|
||||
// $string now contains the first full address/group.
|
||||
// Add to the addresses array.
|
||||
$this->addresses[] = array(
|
||||
'address' => trim($string),
|
||||
'group' => $is_group
|
||||
);
|
||||
|
||||
// Remove the now stored address from the initial line, the +1
|
||||
// is to account for the explode character.
|
||||
$address = trim(substr($address, strlen($string) + 1));
|
||||
|
||||
// If the next char is a comma and this was a group, then
|
||||
// there are more addresses, otherwise, if there are any more
|
||||
// chars, then there is another address.
|
||||
if ($is_group && substr($address, 0, 1) == ','){
|
||||
$address = trim(substr($address, 1));
|
||||
return $address;
|
||||
|
||||
} elseif (strlen($address) > 0) {
|
||||
return $address;
|
||||
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If you got here then something's off
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a group at the start of the string.
|
||||
*
|
||||
* @access private
|
||||
* @param string $address The address to check.
|
||||
* @return boolean Whether or not there is a group at the start of the string.
|
||||
*/
|
||||
function _isGroup($address)
|
||||
{
|
||||
// First comma not in quotes, angles or escaped:
|
||||
$parts = explode(',', $address);
|
||||
$string = $this->_splitCheck($parts, ',');
|
||||
|
||||
// Now we have the first address, we can reliably check for a
|
||||
// group by searching for a colon that's not escaped or in
|
||||
// quotes or angle brackets.
|
||||
if (count($parts = explode(':', $string)) > 1) {
|
||||
$string2 = $this->_splitCheck($parts, ':');
|
||||
return ($string2 !== $string);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A common function that will check an exploded string.
|
||||
*
|
||||
* @access private
|
||||
* @param array $parts The exloded string.
|
||||
* @param string $char The char that was exploded on.
|
||||
* @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
|
||||
*/
|
||||
function _splitCheck($parts, $char)
|
||||
{
|
||||
$string = $parts[0];
|
||||
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
if ($this->_hasUnclosedQuotes($string)
|
||||
|| $this->_hasUnclosedBrackets($string, '<>')
|
||||
|| $this->_hasUnclosedBrackets($string, '[]')
|
||||
|| $this->_hasUnclosedBrackets($string, '()')
|
||||
|| substr($string, -1) == '\\') {
|
||||
if (isset($parts[$i + 1])) {
|
||||
$string = $string . $char . $parts[$i + 1];
|
||||
} else {
|
||||
$this->error = 'Invalid address spec. Unclosed bracket or quotes';
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->index = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string has unclosed quotes or not.
|
||||
*
|
||||
* @access private
|
||||
* @param string $string The string to check.
|
||||
* @return boolean True if there are unclosed quotes inside the string,
|
||||
* false otherwise.
|
||||
*/
|
||||
function _hasUnclosedQuotes($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$iMax = strlen($string);
|
||||
$in_quote = false;
|
||||
$i = $slashes = 0;
|
||||
|
||||
for (; $i < $iMax; ++$i) {
|
||||
switch ($string[$i]) {
|
||||
case '\\':
|
||||
++$slashes;
|
||||
break;
|
||||
|
||||
case '"':
|
||||
if ($slashes % 2 == 0) {
|
||||
$in_quote = !$in_quote;
|
||||
}
|
||||
// Fall through to default action below.
|
||||
|
||||
default:
|
||||
$slashes = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $in_quote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string has an unclosed brackets or not. IMPORTANT:
|
||||
* This function handles both angle brackets and square brackets;
|
||||
*
|
||||
* @access private
|
||||
* @param string $string The string to check.
|
||||
* @param string $chars The characters to check for.
|
||||
* @return boolean True if there are unclosed brackets inside the string, false otherwise.
|
||||
*/
|
||||
function _hasUnclosedBrackets($string, $chars)
|
||||
{
|
||||
$num_angle_start = substr_count($string, $chars[0]);
|
||||
$num_angle_end = substr_count($string, $chars[1]);
|
||||
|
||||
$this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
|
||||
$this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
|
||||
|
||||
if ($num_angle_start < $num_angle_end) {
|
||||
$this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
|
||||
return false;
|
||||
} else {
|
||||
return ($num_angle_start > $num_angle_end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub function that is used only by hasUnclosedBrackets().
|
||||
*
|
||||
* @access private
|
||||
* @param string $string The string to check.
|
||||
* @param integer &$num The number of occurences.
|
||||
* @param string $char The character to count.
|
||||
* @return integer The number of occurences of $char in $string, adjusted for backslashes.
|
||||
*/
|
||||
function _hasUnclosedBracketsSub($string, &$num, $char)
|
||||
{
|
||||
$parts = explode($char, $string);
|
||||
for ($i = 0; $i < count($parts); $i++){
|
||||
if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
|
||||
$num--;
|
||||
if (isset($parts[$i + 1]))
|
||||
$parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
|
||||
}
|
||||
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to begin checking the address.
|
||||
*
|
||||
* @access private
|
||||
* @param string $address The address to validate.
|
||||
* @return mixed False on failure, or a structured array of address information on success.
|
||||
*/
|
||||
function _validateAddress($address)
|
||||
{
|
||||
$is_group = false;
|
||||
$addresses = array();
|
||||
|
||||
if ($address['group']) {
|
||||
$is_group = true;
|
||||
|
||||
// Get the group part of the name
|
||||
$parts = explode(':', $address['address']);
|
||||
$groupname = $this->_splitCheck($parts, ':');
|
||||
$structure = array();
|
||||
|
||||
// And validate the group part of the name.
|
||||
if (!$this->_validatePhrase($groupname)){
|
||||
$this->error = 'Group name did not validate.';
|
||||
return false;
|
||||
} else {
|
||||
// Don't include groups if we are not nesting
|
||||
// them. This avoids returning invalid addresses.
|
||||
if ($this->nestGroups) {
|
||||
$structure = new stdClass;
|
||||
$structure->groupname = $groupname;
|
||||
}
|
||||
}
|
||||
|
||||
$address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
|
||||
}
|
||||
|
||||
// If a group then split on comma and put into an array.
|
||||
// Otherwise, Just put the whole address in an array.
|
||||
if ($is_group) {
|
||||
while (strlen($address['address']) > 0) {
|
||||
$parts = explode(',', $address['address']);
|
||||
$addresses[] = $this->_splitCheck($parts, ',');
|
||||
$address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
|
||||
}
|
||||
} else {
|
||||
$addresses[] = $address['address'];
|
||||
}
|
||||
|
||||
// Check that $addresses is set, if address like this:
|
||||
// Groupname:;
|
||||
// Then errors were appearing.
|
||||
if (!count($addresses)){
|
||||
$this->error = 'Empty group.';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trim the whitespace from all of the address strings.
|
||||
array_map('trim', $addresses);
|
||||
|
||||
// Validate each mailbox.
|
||||
// Format could be one of: name <geezer@domain.com>
|
||||
// geezer@domain.com
|
||||
// geezer
|
||||
// ... or any other format valid by RFC 822.
|
||||
for ($i = 0; $i < count($addresses); $i++) {
|
||||
if (!$this->validateMailbox($addresses[$i])) {
|
||||
if (empty($this->error)) {
|
||||
$this->error = 'Validation failed for: ' . $addresses[$i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Nested format
|
||||
if ($this->nestGroups) {
|
||||
if ($is_group) {
|
||||
$structure->addresses = $addresses;
|
||||
} else {
|
||||
$structure = $addresses[0];
|
||||
}
|
||||
|
||||
// Flat format
|
||||
} else {
|
||||
if ($is_group) {
|
||||
$structure = array_merge($structure, $addresses);
|
||||
} else {
|
||||
$structure = $addresses;
|
||||
}
|
||||
}
|
||||
|
||||
return $structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a phrase.
|
||||
*
|
||||
* @access private
|
||||
* @param string $phrase The phrase to check.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _validatePhrase($phrase)
|
||||
{
|
||||
// Splits on one or more Tab or space.
|
||||
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
$phrase_parts = array();
|
||||
while (count($parts) > 0){
|
||||
$phrase_parts[] = $this->_splitCheck($parts, ' ');
|
||||
for ($i = 0; $i < $this->index + 1; $i++)
|
||||
array_shift($parts);
|
||||
}
|
||||
|
||||
foreach ($phrase_parts as $part) {
|
||||
// If quoted string:
|
||||
if (substr($part, 0, 1) == '"') {
|
||||
if (!$this->_validateQuotedString($part)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise it's an atom:
|
||||
if (!$this->_validateAtom($part)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate an atom which from rfc822 is:
|
||||
* atom = 1*<any CHAR except specials, SPACE and CTLs>
|
||||
*
|
||||
* If validation ($this->validate) has been turned off, then
|
||||
* validateAtom() doesn't actually check anything. This is so that you
|
||||
* can split a list of addresses up before encoding personal names
|
||||
* (umlauts, etc.), for example.
|
||||
*
|
||||
* @access private
|
||||
* @param string $atom The string to check.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _validateAtom($atom)
|
||||
{
|
||||
if (!$this->validate) {
|
||||
// Validation has been turned off; assume the atom is okay.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for any char from ASCII 0 - ASCII 127
|
||||
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for specials:
|
||||
if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for control characters (ASCII 0-31):
|
||||
if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate quoted string, which is:
|
||||
* quoted-string = <"> *(qtext/quoted-pair) <">
|
||||
*
|
||||
* @access private
|
||||
* @param string $qstring The string to check
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _validateQuotedString($qstring)
|
||||
{
|
||||
// Leading and trailing "
|
||||
$qstring = substr($qstring, 1, -1);
|
||||
|
||||
// Perform check, removing quoted characters first.
|
||||
return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a mailbox, which is:
|
||||
* mailbox = addr-spec ; simple address
|
||||
* / phrase route-addr ; name and route-addr
|
||||
*
|
||||
* @access public
|
||||
* @param string &$mailbox The string to check.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function validateMailbox(&$mailbox)
|
||||
{
|
||||
// A couple of defaults.
|
||||
$phrase = '';
|
||||
$comment = '';
|
||||
$comments = array();
|
||||
|
||||
// Catch any RFC822 comments and store them separately.
|
||||
$_mailbox = $mailbox;
|
||||
while (strlen(trim($_mailbox)) > 0) {
|
||||
$parts = explode('(', $_mailbox);
|
||||
$before_comment = $this->_splitCheck($parts, '(');
|
||||
if ($before_comment != $_mailbox) {
|
||||
// First char should be a (.
|
||||
$comment = substr(str_replace($before_comment, '', $_mailbox), 1);
|
||||
$parts = explode(')', $comment);
|
||||
$comment = $this->_splitCheck($parts, ')');
|
||||
$comments[] = $comment;
|
||||
|
||||
// +1 is for the trailing )
|
||||
$_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($comments as $comment) {
|
||||
$mailbox = str_replace("($comment)", '', $mailbox);
|
||||
}
|
||||
|
||||
$mailbox = trim($mailbox);
|
||||
|
||||
// Check for name + route-addr
|
||||
if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
|
||||
$parts = explode('<', $mailbox);
|
||||
$name = $this->_splitCheck($parts, '<');
|
||||
|
||||
$phrase = trim($name);
|
||||
$route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
|
||||
|
||||
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only got addr-spec
|
||||
} else {
|
||||
// First snip angle brackets if present.
|
||||
if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
|
||||
$addr_spec = substr($mailbox, 1, -1);
|
||||
} else {
|
||||
$addr_spec = $mailbox;
|
||||
}
|
||||
|
||||
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the object that will be returned.
|
||||
$mbox = new stdClass();
|
||||
|
||||
// Add the phrase (even if empty) and comments
|
||||
$mbox->personal = $phrase;
|
||||
$mbox->comment = isset($comments) ? $comments : array();
|
||||
|
||||
if (isset($route_addr)) {
|
||||
$mbox->mailbox = $route_addr['local_part'];
|
||||
$mbox->host = $route_addr['domain'];
|
||||
$route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
|
||||
} else {
|
||||
$mbox->mailbox = $addr_spec['local_part'];
|
||||
$mbox->host = $addr_spec['domain'];
|
||||
}
|
||||
|
||||
$mailbox = $mbox;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function validates a route-addr which is:
|
||||
* route-addr = "<" [route] addr-spec ">"
|
||||
*
|
||||
* Angle brackets have already been removed at the point of
|
||||
* getting to this function.
|
||||
*
|
||||
* @access private
|
||||
* @param string $route_addr The string to check.
|
||||
* @return mixed False on failure, or an array containing validated address/route information on success.
|
||||
*/
|
||||
function _validateRouteAddr($route_addr)
|
||||
{
|
||||
// Check for colon.
|
||||
if (strpos($route_addr, ':') !== false) {
|
||||
$parts = explode(':', $route_addr);
|
||||
$route = $this->_splitCheck($parts, ':');
|
||||
} else {
|
||||
$route = $route_addr;
|
||||
}
|
||||
|
||||
// If $route is same as $route_addr then the colon was in
|
||||
// quotes or brackets or, of course, non existent.
|
||||
if ($route === $route_addr){
|
||||
unset($route);
|
||||
$addr_spec = $route_addr;
|
||||
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Validate route part.
|
||||
if (($route = $this->_validateRoute($route)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$addr_spec = substr($route_addr, strlen($route . ':'));
|
||||
|
||||
// Validate addr-spec part.
|
||||
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($route)) {
|
||||
$return['adl'] = $route;
|
||||
} else {
|
||||
$return['adl'] = '';
|
||||
}
|
||||
|
||||
$return = array_merge($return, $addr_spec);
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a route, which is:
|
||||
* route = 1#("@" domain) ":"
|
||||
*
|
||||
* @access private
|
||||
* @param string $route The string to check.
|
||||
* @return mixed False on failure, or the validated $route on success.
|
||||
*/
|
||||
function _validateRoute($route)
|
||||
{
|
||||
// Split on comma.
|
||||
$domains = explode(',', trim($route));
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$domain = str_replace('@', '', trim($domain));
|
||||
if (!$this->_validateDomain($domain)) return false;
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a domain, though this is not quite what
|
||||
* you expect of a strict internet domain.
|
||||
*
|
||||
* domain = sub-domain *("." sub-domain)
|
||||
*
|
||||
* @access private
|
||||
* @param string $domain The string to check.
|
||||
* @return mixed False on failure, or the validated domain on success.
|
||||
*/
|
||||
function _validateDomain($domain)
|
||||
{
|
||||
// Note the different use of $subdomains and $sub_domains
|
||||
$subdomains = explode('.', $domain);
|
||||
|
||||
while (count($subdomains) > 0) {
|
||||
$sub_domains[] = $this->_splitCheck($subdomains, '.');
|
||||
for ($i = 0; $i < $this->index + 1; $i++)
|
||||
array_shift($subdomains);
|
||||
}
|
||||
|
||||
foreach ($sub_domains as $sub_domain) {
|
||||
if (!$this->_validateSubdomain(trim($sub_domain)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Managed to get here, so return input.
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a subdomain:
|
||||
* subdomain = domain-ref / domain-literal
|
||||
*
|
||||
* @access private
|
||||
* @param string $subdomain The string to check.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _validateSubdomain($subdomain)
|
||||
{
|
||||
if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
|
||||
if (!$this->_validateDliteral($arr[1])) return false;
|
||||
} else {
|
||||
if (!$this->_validateAtom($subdomain)) return false;
|
||||
}
|
||||
|
||||
// Got here, so return successful.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate a domain literal:
|
||||
* domain-literal = "[" *(dtext / quoted-pair) "]"
|
||||
*
|
||||
* @access private
|
||||
* @param string $dliteral The string to check.
|
||||
* @return boolean Success or failure.
|
||||
*/
|
||||
function _validateDliteral($dliteral)
|
||||
{
|
||||
return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate an addr-spec.
|
||||
*
|
||||
* addr-spec = local-part "@" domain
|
||||
*
|
||||
* @access private
|
||||
* @param string $addr_spec The string to check.
|
||||
* @return mixed False on failure, or the validated addr-spec on success.
|
||||
*/
|
||||
function _validateAddrSpec($addr_spec)
|
||||
{
|
||||
$addr_spec = trim($addr_spec);
|
||||
|
||||
// Split on @ sign if there is one.
|
||||
if (strpos($addr_spec, '@') !== false) {
|
||||
$parts = explode('@', $addr_spec);
|
||||
$local_part = $this->_splitCheck($parts, '@');
|
||||
$domain = substr($addr_spec, strlen($local_part . '@'));
|
||||
|
||||
// No @ sign so assume the default domain.
|
||||
} else {
|
||||
$local_part = $addr_spec;
|
||||
$domain = $this->default_domain;
|
||||
}
|
||||
|
||||
if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
|
||||
if (($domain = $this->_validateDomain($domain)) === false) return false;
|
||||
|
||||
// Got here so return successful.
|
||||
return array('local_part' => $local_part, 'domain' => $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to validate the local part of an address:
|
||||
* local-part = word *("." word)
|
||||
*
|
||||
* @access private
|
||||
* @param string $local_part
|
||||
* @return mixed False on failure, or the validated local part on success.
|
||||
*/
|
||||
function _validateLocalPart($local_part)
|
||||
{
|
||||
$parts = explode('.', $local_part);
|
||||
$words = array();
|
||||
|
||||
// Split the local_part into words.
|
||||
while (count($parts) > 0){
|
||||
$words[] = $this->_splitCheck($parts, '.');
|
||||
for ($i = 0; $i < $this->index + 1; $i++) {
|
||||
array_shift($parts);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each word.
|
||||
foreach ($words as $word) {
|
||||
// If this word contains an unquoted space, it is invalid. (6.2.4)
|
||||
if (strpos($word, ' ') && $word[0] !== '"')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->_validatePhrase(trim($word)) === false) return false;
|
||||
}
|
||||
|
||||
// Managed to get here, so return the input.
|
||||
return $local_part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an approximate count of how many addresses are in the
|
||||
* given string. This is APPROXIMATE as it only splits based on a
|
||||
* comma which has no preceding backslash. Could be useful as
|
||||
* large amounts of addresses will end up producing *large*
|
||||
* structures when used with parseAddressList().
|
||||
*
|
||||
* @param string $data Addresses to count
|
||||
* @return int Approximate count
|
||||
*/
|
||||
function approximateCount($data)
|
||||
{
|
||||
return count(preg_split('/(?<!\\\\),/', $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a email validating function separate to the rest of the
|
||||
* class. It simply validates whether an email is of the common
|
||||
* internet form: <user>@<domain>. This can be sufficient for most
|
||||
* people. Optional stricter mode can be utilised which restricts
|
||||
* mailbox characters allowed to alphanumeric, full stop, hyphen
|
||||
* and underscore.
|
||||
*
|
||||
* @param string $data Address to check
|
||||
* @param boolean $strict Optional stricter mode
|
||||
* @return mixed False if it fails, an indexed array
|
||||
* username/domain if it matches
|
||||
*/
|
||||
function isValidInetAddress($data, $strict = false)
|
||||
{
|
||||
$regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
|
||||
if (preg_match($regex, trim($data), $matches)) {
|
||||
return array($matches[1], $matches[2]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
143
extlib/Mail/mail.php
Normal file
143
extlib/Mail/mail.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
//
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Author: Chuck Hagenbuch <chuck@horde.org> |
|
||||
// +----------------------------------------------------------------------+
|
||||
//
|
||||
// $Id: mail.php,v 1.20 2007/10/06 17:00:00 chagenbu Exp $
|
||||
|
||||
/**
|
||||
* internal PHP-mail() implementation of the PEAR Mail:: interface.
|
||||
* @package Mail
|
||||
* @version $Revision: 1.20 $
|
||||
*/
|
||||
class Mail_mail extends Mail {
|
||||
|
||||
/**
|
||||
* Any arguments to pass to the mail() function.
|
||||
* @var string
|
||||
*/
|
||||
var $_params = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Instantiates a new Mail_mail:: object based on the parameters
|
||||
* passed in.
|
||||
*
|
||||
* @param array $params Extra arguments for the mail() function.
|
||||
*/
|
||||
function Mail_mail($params = null)
|
||||
{
|
||||
// The other mail implementations accept parameters as arrays.
|
||||
// In the interest of being consistent, explode an array into
|
||||
// a string of parameter arguments.
|
||||
if (is_array($params)) {
|
||||
$this->_params = join(' ', $params);
|
||||
} else {
|
||||
$this->_params = $params;
|
||||
}
|
||||
|
||||
/* Because the mail() function may pass headers as command
|
||||
* line arguments, we can't guarantee the use of the standard
|
||||
* "\r\n" separator. Instead, we use the system's native line
|
||||
* separator. */
|
||||
if (defined('PHP_EOL')) {
|
||||
$this->sep = PHP_EOL;
|
||||
} else {
|
||||
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Mail_mail::send() function using php's built-in mail()
|
||||
* command.
|
||||
*
|
||||
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||
* (RFC822 compliant), or an array of recipients,
|
||||
* each RFC822 valid. This may contain recipients not
|
||||
* specified in the headers, for Bcc:, resending
|
||||
* messages, etc.
|
||||
*
|
||||
* @param array $headers The array of headers to send with the mail, in an
|
||||
* associative array, where the array key is the
|
||||
* header name (ie, 'Subject'), and the array value
|
||||
* is the header value (ie, 'test'). The header
|
||||
* produced from those values would be 'Subject:
|
||||
* test'.
|
||||
*
|
||||
* @param string $body The full text of the message body, including any
|
||||
* Mime parts, etc.
|
||||
*
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
* containing a descriptive error message on
|
||||
* failure.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
if (!is_array($headers)) {
|
||||
return PEAR::raiseError('$headers must be an array');
|
||||
}
|
||||
|
||||
$result = $this->_sanitizeHeaders($headers);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// If we're passed an array of recipients, implode it.
|
||||
if (is_array($recipients)) {
|
||||
$recipients = implode(', ', $recipients);
|
||||
}
|
||||
|
||||
// Get the Subject out of the headers array so that we can
|
||||
// pass it as a seperate argument to mail().
|
||||
$subject = '';
|
||||
if (isset($headers['Subject'])) {
|
||||
$subject = $headers['Subject'];
|
||||
unset($headers['Subject']);
|
||||
}
|
||||
|
||||
// Also remove the To: header. The mail() function will add its own
|
||||
// To: header based on the contents of $recipients.
|
||||
unset($headers['To']);
|
||||
|
||||
// Flatten the headers out.
|
||||
$headerElements = $this->prepareHeaders($headers);
|
||||
if (is_a($headerElements, 'PEAR_Error')) {
|
||||
return $headerElements;
|
||||
}
|
||||
list(, $text_headers) = $headerElements;
|
||||
|
||||
// We only use mail()'s optional fifth parameter if the additional
|
||||
// parameters have been provided and we're not running in safe mode.
|
||||
if (empty($this->_params) || ini_get('safe_mode')) {
|
||||
$result = mail($recipients, $subject, $body, $text_headers);
|
||||
} else {
|
||||
$result = mail($recipients, $subject, $body, $text_headers,
|
||||
$this->_params);
|
||||
}
|
||||
|
||||
// If the mail() function returned failure, we need to create a
|
||||
// PEAR_Error object and return it instead of the boolean result.
|
||||
if ($result === false) {
|
||||
$result = PEAR::raiseError('mail() returned failure');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
119
extlib/Mail/mock.php
Normal file
119
extlib/Mail/mock.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
//
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Author: Chuck Hagenbuch <chuck@horde.org> |
|
||||
// +----------------------------------------------------------------------+
|
||||
//
|
||||
// $Id: mock.php,v 1.1 2007/12/08 17:57:54 chagenbu Exp $
|
||||
//
|
||||
|
||||
/**
|
||||
* Mock implementation of the PEAR Mail:: interface for testing.
|
||||
* @access public
|
||||
* @package Mail
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
class Mail_mock extends Mail {
|
||||
|
||||
/**
|
||||
* Array of messages that have been sent with the mock.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
var $sentMessages = array();
|
||||
|
||||
/**
|
||||
* Callback before sending mail.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
var $_preSendCallback;
|
||||
|
||||
/**
|
||||
* Callback after sending mai.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
var $_postSendCallback;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Instantiates a new Mail_mock:: object based on the parameters
|
||||
* passed in. It looks for the following parameters, both optional:
|
||||
* preSendCallback Called before an email would be sent.
|
||||
* postSendCallback Called after an email would have been sent.
|
||||
*
|
||||
* @param array Hash containing any parameters.
|
||||
* @access public
|
||||
*/
|
||||
function Mail_mock($params)
|
||||
{
|
||||
if (isset($params['preSendCallback']) &&
|
||||
is_callable($params['preSendCallback'])) {
|
||||
$this->_preSendCallback = $params['preSendCallback'];
|
||||
}
|
||||
|
||||
if (isset($params['postSendCallback']) &&
|
||||
is_callable($params['postSendCallback'])) {
|
||||
$this->_postSendCallback = $params['postSendCallback'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Mail_mock::send() function. Silently discards all
|
||||
* mail.
|
||||
*
|
||||
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||
* (RFC822 compliant), or an array of recipients,
|
||||
* each RFC822 valid. This may contain recipients not
|
||||
* specified in the headers, for Bcc:, resending
|
||||
* messages, etc.
|
||||
*
|
||||
* @param array $headers The array of headers to send with the mail, in an
|
||||
* associative array, where the array key is the
|
||||
* header name (ie, 'Subject'), and the array value
|
||||
* is the header value (ie, 'test'). The header
|
||||
* produced from those values would be 'Subject:
|
||||
* test'.
|
||||
*
|
||||
* @param string $body The full text of the message body, including any
|
||||
* Mime parts, etc.
|
||||
*
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
* containing a descriptive error message on
|
||||
* failure.
|
||||
* @access public
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
if ($this->_preSendCallback) {
|
||||
call_user_func_array($this->_preSendCallback,
|
||||
array(&$this, $recipients, $headers, $body));
|
||||
}
|
||||
|
||||
$entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body);
|
||||
$this->sentMessages[] = $entry;
|
||||
|
||||
if ($this->_postSendCallback) {
|
||||
call_user_func_array($this->_postSendCallback,
|
||||
array(&$this, $recipients, $headers, $body));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
60
extlib/Mail/null.php
Normal file
60
extlib/Mail/null.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
//
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Author: Phil Kernick <philk@rotfl.com.au> |
|
||||
// +----------------------------------------------------------------------+
|
||||
//
|
||||
// $Id: null.php,v 1.2 2004/04/06 05:19:03 jon Exp $
|
||||
//
|
||||
|
||||
/**
|
||||
* Null implementation of the PEAR Mail:: interface.
|
||||
* @access public
|
||||
* @package Mail
|
||||
* @version $Revision: 1.2 $
|
||||
*/
|
||||
class Mail_null extends Mail {
|
||||
|
||||
/**
|
||||
* Implements Mail_null::send() function. Silently discards all
|
||||
* mail.
|
||||
*
|
||||
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||
* (RFC822 compliant), or an array of recipients,
|
||||
* each RFC822 valid. This may contain recipients not
|
||||
* specified in the headers, for Bcc:, resending
|
||||
* messages, etc.
|
||||
*
|
||||
* @param array $headers The array of headers to send with the mail, in an
|
||||
* associative array, where the array key is the
|
||||
* header name (ie, 'Subject'), and the array value
|
||||
* is the header value (ie, 'test'). The header
|
||||
* produced from those values would be 'Subject:
|
||||
* test'.
|
||||
*
|
||||
* @param string $body The full text of the message body, including any
|
||||
* Mime parts, etc.
|
||||
*
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
* containing a descriptive error message on
|
||||
* failure.
|
||||
* @access public
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
170
extlib/Mail/sendmail.php
Normal file
170
extlib/Mail/sendmail.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
//
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Author: Chuck Hagenbuch <chuck@horde.org> |
|
||||
// +----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* Sendmail implementation of the PEAR Mail:: interface.
|
||||
* @access public
|
||||
* @package Mail
|
||||
* @version $Revision: 1.19 $
|
||||
*/
|
||||
class Mail_sendmail extends Mail {
|
||||
|
||||
/**
|
||||
* The location of the sendmail or sendmail wrapper binary on the
|
||||
* filesystem.
|
||||
* @var string
|
||||
*/
|
||||
var $sendmail_path = '/usr/sbin/sendmail';
|
||||
|
||||
/**
|
||||
* Any extra command-line parameters to pass to the sendmail or
|
||||
* sendmail wrapper binary.
|
||||
* @var string
|
||||
*/
|
||||
var $sendmail_args = '-i';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Instantiates a new Mail_sendmail:: object based on the parameters
|
||||
* passed in. It looks for the following parameters:
|
||||
* sendmail_path The location of the sendmail binary on the
|
||||
* filesystem. Defaults to '/usr/sbin/sendmail'.
|
||||
*
|
||||
* sendmail_args Any extra parameters to pass to the sendmail
|
||||
* or sendmail wrapper binary.
|
||||
*
|
||||
* If a parameter is present in the $params array, it replaces the
|
||||
* default.
|
||||
*
|
||||
* @param array $params Hash containing any parameters different from the
|
||||
* defaults.
|
||||
* @access public
|
||||
*/
|
||||
function Mail_sendmail($params)
|
||||
{
|
||||
if (isset($params['sendmail_path'])) {
|
||||
$this->sendmail_path = $params['sendmail_path'];
|
||||
}
|
||||
if (isset($params['sendmail_args'])) {
|
||||
$this->sendmail_args = $params['sendmail_args'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Because we need to pass message headers to the sendmail program on
|
||||
* the commandline, we can't guarantee the use of the standard "\r\n"
|
||||
* separator. Instead, we use the system's native line separator.
|
||||
*/
|
||||
if (defined('PHP_EOL')) {
|
||||
$this->sep = PHP_EOL;
|
||||
} else {
|
||||
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Mail::send() function using the sendmail
|
||||
* command-line binary.
|
||||
*
|
||||
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||
* (RFC822 compliant), or an array of recipients,
|
||||
* each RFC822 valid. This may contain recipients not
|
||||
* specified in the headers, for Bcc:, resending
|
||||
* messages, etc.
|
||||
*
|
||||
* @param array $headers The array of headers to send with the mail, in an
|
||||
* associative array, where the array key is the
|
||||
* header name (ie, 'Subject'), and the array value
|
||||
* is the header value (ie, 'test'). The header
|
||||
* produced from those values would be 'Subject:
|
||||
* test'.
|
||||
*
|
||||
* @param string $body The full text of the message body, including any
|
||||
* Mime parts, etc.
|
||||
*
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
* containing a descriptive error message on
|
||||
* failure.
|
||||
* @access public
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
if (!is_array($headers)) {
|
||||
return PEAR::raiseError('$headers must be an array');
|
||||
}
|
||||
|
||||
$result = $this->_sanitizeHeaders($headers);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$recipients = $this->parseRecipients($recipients);
|
||||
if (is_a($recipients, 'PEAR_Error')) {
|
||||
return $recipients;
|
||||
}
|
||||
$recipients = escapeShellCmd(implode(' ', $recipients));
|
||||
|
||||
$headerElements = $this->prepareHeaders($headers);
|
||||
if (is_a($headerElements, 'PEAR_Error')) {
|
||||
return $headerElements;
|
||||
}
|
||||
list($from, $text_headers) = $headerElements;
|
||||
|
||||
/* Since few MTAs are going to allow this header to be forged
|
||||
* unless it's in the MAIL FROM: exchange, we'll use
|
||||
* Return-Path instead of From: if it's set. */
|
||||
if (!empty($headers['Return-Path'])) {
|
||||
$from = $headers['Return-Path'];
|
||||
}
|
||||
|
||||
if (!isset($from)) {
|
||||
return PEAR::raiseError('No from address given.');
|
||||
} elseif (strpos($from, ' ') !== false ||
|
||||
strpos($from, ';') !== false ||
|
||||
strpos($from, '&') !== false ||
|
||||
strpos($from, '`') !== false) {
|
||||
return PEAR::raiseError('From address specified with dangerous characters.');
|
||||
}
|
||||
|
||||
$from = escapeShellCmd($from);
|
||||
$mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
|
||||
if (!$mail) {
|
||||
return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
|
||||
}
|
||||
|
||||
// Write the headers following by two newlines: one to end the headers
|
||||
// section and a second to separate the headers block from the body.
|
||||
fputs($mail, $text_headers . $this->sep . $this->sep);
|
||||
|
||||
fputs($mail, $body);
|
||||
$result = pclose($mail);
|
||||
if (version_compare(phpversion(), '4.2.3') == -1) {
|
||||
// With older php versions, we need to shift the pclose
|
||||
// result to get the exit code.
|
||||
$result = $result >> 8 & 0xFF;
|
||||
}
|
||||
|
||||
if ($result != 0) {
|
||||
return PEAR::raiseError('sendmail returned error code ' . $result,
|
||||
$result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
407
extlib/Mail/smtp.php
Normal file
407
extlib/Mail/smtp.php
Normal file
@ -0,0 +1,407 @@
|
||||
<?php
|
||||
//
|
||||
// +----------------------------------------------------------------------+
|
||||
// | PHP Version 4 |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Copyright (c) 1997-2003 The PHP Group |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | This source file is subject to version 2.02 of the PHP license, |
|
||||
// | that is bundled with this package in the file LICENSE, and is |
|
||||
// | available at through the world-wide-web at |
|
||||
// | http://www.php.net/license/2_02.txt. |
|
||||
// | If you did not receive a copy of the PHP license and are unable to |
|
||||
// | obtain it through the world-wide-web, please send a note to |
|
||||
// | license@php.net so we can mail you a copy immediately. |
|
||||
// +----------------------------------------------------------------------+
|
||||
// | Authors: Chuck Hagenbuch <chuck@horde.org> |
|
||||
// | Jon Parise <jon@php.net> |
|
||||
// +----------------------------------------------------------------------+
|
||||
|
||||
/** Error: Failed to create a Net_SMTP object */
|
||||
define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
|
||||
|
||||
/** Error: Failed to connect to SMTP server */
|
||||
define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
|
||||
|
||||
/** Error: SMTP authentication failure */
|
||||
define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
|
||||
|
||||
/** Error: No From: address has been provided */
|
||||
define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
|
||||
|
||||
/** Error: Failed to set sender */
|
||||
define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
|
||||
|
||||
/** Error: Failed to add recipient */
|
||||
define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
|
||||
|
||||
/** Error: Failed to send data */
|
||||
define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
|
||||
|
||||
/**
|
||||
* SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
|
||||
* @access public
|
||||
* @package Mail
|
||||
* @version $Revision: 1.33 $
|
||||
*/
|
||||
class Mail_smtp extends Mail {
|
||||
|
||||
/**
|
||||
* SMTP connection object.
|
||||
*
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
var $_smtp = null;
|
||||
|
||||
/**
|
||||
* The list of service extension parameters to pass to the Net_SMTP
|
||||
* mailFrom() command.
|
||||
* @var array
|
||||
*/
|
||||
var $_extparams = array();
|
||||
|
||||
/**
|
||||
* The SMTP host to connect to.
|
||||
* @var string
|
||||
*/
|
||||
var $host = 'localhost';
|
||||
|
||||
/**
|
||||
* The port the SMTP server is on.
|
||||
* @var integer
|
||||
*/
|
||||
var $port = 25;
|
||||
|
||||
/**
|
||||
* Should SMTP authentication be used?
|
||||
*
|
||||
* This value may be set to true, false or the name of a specific
|
||||
* authentication method.
|
||||
*
|
||||
* If the value is set to true, the Net_SMTP package will attempt to use
|
||||
* the best authentication method advertised by the remote SMTP server.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
var $auth = false;
|
||||
|
||||
/**
|
||||
* The username to use if the SMTP server requires authentication.
|
||||
* @var string
|
||||
*/
|
||||
var $username = '';
|
||||
|
||||
/**
|
||||
* The password to use if the SMTP server requires authentication.
|
||||
* @var string
|
||||
*/
|
||||
var $password = '';
|
||||
|
||||
/**
|
||||
* Hostname or domain that will be sent to the remote SMTP server in the
|
||||
* HELO / EHLO message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
var $localhost = 'localhost';
|
||||
|
||||
/**
|
||||
* SMTP connection timeout value. NULL indicates no timeout.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
var $timeout = null;
|
||||
|
||||
/**
|
||||
* Turn on Net_SMTP debugging?
|
||||
*
|
||||
* @var boolean $debug
|
||||
*/
|
||||
var $debug = false;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the SMTP connection should persist over
|
||||
* multiple calls to the send() method.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $persist = false;
|
||||
|
||||
/**
|
||||
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
|
||||
* supports it. This speeds up delivery over high-latency connections. By
|
||||
* default, use the default value supplied by Net_SMTP.
|
||||
* @var bool
|
||||
*/
|
||||
var $pipelining;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Instantiates a new Mail_smtp:: object based on the parameters
|
||||
* passed in. It looks for the following parameters:
|
||||
* host The server to connect to. Defaults to localhost.
|
||||
* port The port to connect to. Defaults to 25.
|
||||
* auth SMTP authentication. Defaults to none.
|
||||
* username The username to use for SMTP auth. No default.
|
||||
* password The password to use for SMTP auth. No default.
|
||||
* localhost The local hostname / domain. Defaults to localhost.
|
||||
* timeout The SMTP connection timeout. Defaults to none.
|
||||
* verp Whether to use VERP or not. Defaults to false.
|
||||
* DEPRECATED as of 1.2.0 (use setMailParams()).
|
||||
* debug Activate SMTP debug mode? Defaults to false.
|
||||
* persist Should the SMTP connection persist?
|
||||
* pipelining Use SMTP command pipelining
|
||||
*
|
||||
* If a parameter is present in the $params array, it replaces the
|
||||
* default.
|
||||
*
|
||||
* @param array Hash containing any parameters different from the
|
||||
* defaults.
|
||||
* @access public
|
||||
*/
|
||||
function Mail_smtp($params)
|
||||
{
|
||||
if (isset($params['host'])) $this->host = $params['host'];
|
||||
if (isset($params['port'])) $this->port = $params['port'];
|
||||
if (isset($params['auth'])) $this->auth = $params['auth'];
|
||||
if (isset($params['username'])) $this->username = $params['username'];
|
||||
if (isset($params['password'])) $this->password = $params['password'];
|
||||
if (isset($params['localhost'])) $this->localhost = $params['localhost'];
|
||||
if (isset($params['timeout'])) $this->timeout = $params['timeout'];
|
||||
if (isset($params['debug'])) $this->debug = (bool)$params['debug'];
|
||||
if (isset($params['persist'])) $this->persist = (bool)$params['persist'];
|
||||
if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining'];
|
||||
|
||||
// Deprecated options
|
||||
if (isset($params['verp'])) {
|
||||
$this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
|
||||
}
|
||||
|
||||
register_shutdown_function(array(&$this, '_Mail_smtp'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor implementation to ensure that we disconnect from any
|
||||
* potentially-alive persistent SMTP connections.
|
||||
*/
|
||||
function _Mail_smtp()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Mail::send() function using SMTP.
|
||||
*
|
||||
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||
* (RFC822 compliant), or an array of recipients,
|
||||
* each RFC822 valid. This may contain recipients not
|
||||
* specified in the headers, for Bcc:, resending
|
||||
* messages, etc.
|
||||
*
|
||||
* @param array $headers The array of headers to send with the mail, in an
|
||||
* associative array, where the array key is the
|
||||
* header name (e.g., 'Subject'), and the array value
|
||||
* is the header value (e.g., 'test'). The header
|
||||
* produced from those values would be 'Subject:
|
||||
* test'.
|
||||
*
|
||||
* @param string $body The full text of the message body, including any
|
||||
* MIME parts, etc.
|
||||
*
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
* containing a descriptive error message on
|
||||
* failure.
|
||||
* @access public
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
/* If we don't already have an SMTP object, create one. */
|
||||
$result = &$this->getSMTPObject();
|
||||
if (PEAR::isError($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!is_array($headers)) {
|
||||
return PEAR::raiseError('$headers must be an array');
|
||||
}
|
||||
|
||||
$this->_sanitizeHeaders($headers);
|
||||
|
||||
$headerElements = $this->prepareHeaders($headers);
|
||||
if (is_a($headerElements, 'PEAR_Error')) {
|
||||
$this->_smtp->rset();
|
||||
return $headerElements;
|
||||
}
|
||||
list($from, $textHeaders) = $headerElements;
|
||||
|
||||
/* Since few MTAs are going to allow this header to be forged
|
||||
* unless it's in the MAIL FROM: exchange, we'll use
|
||||
* Return-Path instead of From: if it's set. */
|
||||
if (!empty($headers['Return-Path'])) {
|
||||
$from = $headers['Return-Path'];
|
||||
}
|
||||
|
||||
if (!isset($from)) {
|
||||
$this->_smtp->rset();
|
||||
return PEAR::raiseError('No From: address has been provided',
|
||||
PEAR_MAIL_SMTP_ERROR_FROM);
|
||||
}
|
||||
|
||||
$params = null;
|
||||
if (!empty($this->_extparams)) {
|
||||
foreach ($this->_extparams as $key => $val) {
|
||||
$params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
|
||||
}
|
||||
}
|
||||
if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
|
||||
$error = $this->_error("Failed to set sender: $from", $res);
|
||||
$this->_smtp->rset();
|
||||
return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
|
||||
}
|
||||
|
||||
$recipients = $this->parseRecipients($recipients);
|
||||
if (is_a($recipients, 'PEAR_Error')) {
|
||||
$this->_smtp->rset();
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$res = $this->_smtp->rcptTo($recipient);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$error = $this->_error("Failed to add recipient: $recipient", $res);
|
||||
$this->_smtp->rset();
|
||||
return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the message's headers and the body as SMTP data. */
|
||||
$res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$error = $this->_error('Failed to send data', $res);
|
||||
$this->_smtp->rset();
|
||||
return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
|
||||
}
|
||||
|
||||
/* If persistent connections are disabled, destroy our SMTP object. */
|
||||
if ($this->persist === false) {
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the SMTP server by instantiating a Net_SMTP object.
|
||||
*
|
||||
* @return mixed Returns a reference to the Net_SMTP object on success, or
|
||||
* a PEAR_Error containing a descriptive error message on
|
||||
* failure.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
function &getSMTPObject()
|
||||
{
|
||||
if (is_object($this->_smtp) !== false) {
|
||||
return $this->_smtp;
|
||||
}
|
||||
|
||||
include_once 'Net/SMTP.php';
|
||||
$this->_smtp = &new Net_SMTP($this->host,
|
||||
$this->port,
|
||||
$this->localhost);
|
||||
|
||||
/* If we still don't have an SMTP object at this point, fail. */
|
||||
if (is_object($this->_smtp) === false) {
|
||||
return PEAR::raiseError('Failed to create a Net_SMTP object',
|
||||
PEAR_MAIL_SMTP_ERROR_CREATE);
|
||||
}
|
||||
|
||||
/* Configure the SMTP connection. */
|
||||
if ($this->debug) {
|
||||
$this->_smtp->setDebug(true);
|
||||
}
|
||||
|
||||
/* Attempt to connect to the configured SMTP server. */
|
||||
if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
|
||||
$error = $this->_error('Failed to connect to ' .
|
||||
$this->host . ':' . $this->port,
|
||||
$res);
|
||||
return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
|
||||
}
|
||||
|
||||
/* Attempt to authenticate if authentication has been enabled. */
|
||||
if ($this->auth) {
|
||||
$method = is_string($this->auth) ? $this->auth : '';
|
||||
|
||||
if (PEAR::isError($res = $this->_smtp->auth($this->username,
|
||||
$this->password,
|
||||
$method))) {
|
||||
$error = $this->_error("$method authentication failure",
|
||||
$res);
|
||||
$this->_smtp->rset();
|
||||
return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_smtp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add parameter associated with a SMTP service extension.
|
||||
*
|
||||
* @param string Extension keyword.
|
||||
* @param string Any value the keyword needs.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
function addServiceExtensionParameter($keyword, $value = null)
|
||||
{
|
||||
$this->_extparams[$keyword] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect and destroy the current SMTP connection.
|
||||
*
|
||||
* @return boolean True if the SMTP connection no longer exists.
|
||||
*
|
||||
* @since 1.1.9
|
||||
* @access public
|
||||
*/
|
||||
function disconnect()
|
||||
{
|
||||
/* If we have an SMTP object, disconnect and destroy it. */
|
||||
if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
|
||||
$this->_smtp = null;
|
||||
}
|
||||
|
||||
/* We are disconnected if we no longer have an SMTP object. */
|
||||
return ($this->_smtp === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a standardized string describing the current SMTP error.
|
||||
*
|
||||
* @param string $text Custom string describing the error context.
|
||||
* @param object $error Reference to the current PEAR_Error object.
|
||||
*
|
||||
* @return string A string describing the current SMTP error.
|
||||
*
|
||||
* @since 1.1.7
|
||||
* @access private
|
||||
*/
|
||||
function _error($text, &$error)
|
||||
{
|
||||
/* Split the SMTP response into a code and a response string. */
|
||||
list($code, $response) = $this->_smtp->getResponse();
|
||||
|
||||
/* Build our standardized error string. */
|
||||
return $text
|
||||
. ' [SMTP: ' . $error->getMessage()
|
||||
. " (code: $code, response: $response)]";
|
||||
}
|
||||
|
||||
}
|
478
extlib/Mail/smtpmx.php
Normal file
478
extlib/Mail/smtpmx.php
Normal file
@ -0,0 +1,478 @@
|
||||
<?PHP
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* SMTP MX
|
||||
*
|
||||
* SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
|
||||
*
|
||||
* PHP versions 4 and 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category Mail
|
||||
* @package Mail_smtpmx
|
||||
* @author gERD Schaufelberger <gerd@php-tools.net>
|
||||
* @copyright 1997-2005 The PHP Group
|
||||
* @license http://www.php.net/license/3_0.txt PHP License 3.0
|
||||
* @version CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $
|
||||
* @see Mail
|
||||
*/
|
||||
|
||||
require_once 'Net/SMTP.php';
|
||||
|
||||
/**
|
||||
* SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
|
||||
*
|
||||
*
|
||||
* @access public
|
||||
* @author gERD Schaufelberger <gerd@php-tools.net>
|
||||
* @package Mail
|
||||
* @version $Revision: 1.2 $
|
||||
*/
|
||||
class Mail_smtpmx extends Mail {
|
||||
|
||||
/**
|
||||
* SMTP connection object.
|
||||
*
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
var $_smtp = null;
|
||||
|
||||
/**
|
||||
* The port the SMTP server is on.
|
||||
* @var integer
|
||||
* @see getservicebyname()
|
||||
*/
|
||||
var $port = 25;
|
||||
|
||||
/**
|
||||
* Hostname or domain that will be sent to the remote SMTP server in the
|
||||
* HELO / EHLO message.
|
||||
*
|
||||
* @var string
|
||||
* @see posix_uname()
|
||||
*/
|
||||
var $mailname = 'localhost';
|
||||
|
||||
/**
|
||||
* SMTP connection timeout value. NULL indicates no timeout.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
var $timeout = 10;
|
||||
|
||||
/**
|
||||
* use either PEAR:Net_DNS or getmxrr
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $withNetDns = true;
|
||||
|
||||
/**
|
||||
* PEAR:Net_DNS_Resolver
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
var $resolver;
|
||||
|
||||
/**
|
||||
* Whether to use VERP or not. If not a boolean, the string value
|
||||
* will be used as the VERP separators.
|
||||
*
|
||||
* @var mixed boolean or string
|
||||
*/
|
||||
var $verp = false;
|
||||
|
||||
/**
|
||||
* Whether to use VRFY or not.
|
||||
*
|
||||
* @var boolean $vrfy
|
||||
*/
|
||||
var $vrfy = false;
|
||||
|
||||
/**
|
||||
* Switch to test mode - don't send emails for real
|
||||
*
|
||||
* @var boolean $debug
|
||||
*/
|
||||
var $test = false;
|
||||
|
||||
/**
|
||||
* Turn on Net_SMTP debugging?
|
||||
*
|
||||
* @var boolean $peardebug
|
||||
*/
|
||||
var $debug = false;
|
||||
|
||||
/**
|
||||
* internal error codes
|
||||
*
|
||||
* translate internal error identifier to PEAR-Error codes and human
|
||||
* readable messages.
|
||||
*
|
||||
* @var boolean $debug
|
||||
* @todo as I need unique error-codes to identify what exactly went wrond
|
||||
* I did not use intergers as it should be. Instead I added a "namespace"
|
||||
* for each code. This avoids conflicts with error codes from different
|
||||
* classes. How can I use unique error codes and stay conform with PEAR?
|
||||
*/
|
||||
var $errorCode = array(
|
||||
'not_connected' => array(
|
||||
'code' => 1,
|
||||
'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
|
||||
),
|
||||
'failed_vrfy_rcpt' => array(
|
||||
'code' => 2,
|
||||
'msg' => 'Recipient "{RCPT}" could not be veryfied.'
|
||||
),
|
||||
'failed_set_from' => array(
|
||||
'code' => 3,
|
||||
'msg' => 'Failed to set sender: {FROM}.'
|
||||
),
|
||||
'failed_set_rcpt' => array(
|
||||
'code' => 4,
|
||||
'msg' => 'Failed to set recipient: {RCPT}.'
|
||||
),
|
||||
'failed_send_data' => array(
|
||||
'code' => 5,
|
||||
'msg' => 'Failed to send mail to: {RCPT}.'
|
||||
),
|
||||
'no_from' => array(
|
||||
'code' => 5,
|
||||
'msg' => 'No from address has be provided.'
|
||||
),
|
||||
'send_data' => array(
|
||||
'code' => 7,
|
||||
'msg' => 'Failed to create Net_SMTP object.'
|
||||
),
|
||||
'no_mx' => array(
|
||||
'code' => 8,
|
||||
'msg' => 'No MX-record for {RCPT} found.'
|
||||
),
|
||||
'no_resolver' => array(
|
||||
'code' => 9,
|
||||
'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
|
||||
),
|
||||
'failed_rset' => array(
|
||||
'code' => 10,
|
||||
'msg' => 'RSET command failed, SMTP-connection corrupt.'
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Instantiates a new Mail_smtp:: object based on the parameters
|
||||
* passed in. It looks for the following parameters:
|
||||
* mailname The name of the local mail system (a valid hostname which matches the reverse lookup)
|
||||
* port smtp-port - the default comes from getservicebyname() and should work fine
|
||||
* timeout The SMTP connection timeout. Defaults to 30 seconds.
|
||||
* vrfy Whether to use VRFY or not. Defaults to false.
|
||||
* verp Whether to use VERP or not. Defaults to false.
|
||||
* test Activate test mode? Defaults to false.
|
||||
* debug Activate SMTP and Net_DNS debug mode? Defaults to false.
|
||||
* netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
|
||||
*
|
||||
* If a parameter is present in the $params array, it replaces the
|
||||
* default.
|
||||
*
|
||||
* @access public
|
||||
* @param array Hash containing any parameters different from the
|
||||
* defaults.
|
||||
* @see _Mail_smtpmx()
|
||||
*/
|
||||
function __construct($params)
|
||||
{
|
||||
if (isset($params['mailname'])) {
|
||||
$this->mailname = $params['mailname'];
|
||||
} else {
|
||||
// try to find a valid mailname
|
||||
if (function_exists('posix_uname')) {
|
||||
$uname = posix_uname();
|
||||
$this->mailname = $uname['nodename'];
|
||||
}
|
||||
}
|
||||
|
||||
// port number
|
||||
if (isset($params['port'])) {
|
||||
$this->_port = $params['port'];
|
||||
} else {
|
||||
$this->_port = getservbyname('smtp', 'tcp');
|
||||
}
|
||||
|
||||
if (isset($params['timeout'])) $this->timeout = $params['timeout'];
|
||||
if (isset($params['verp'])) $this->verp = $params['verp'];
|
||||
if (isset($params['test'])) $this->test = $params['test'];
|
||||
if (isset($params['peardebug'])) $this->test = $params['peardebug'];
|
||||
if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor wrapper for PHP4
|
||||
*
|
||||
* @access public
|
||||
* @param array Hash containing any parameters different from the defaults
|
||||
* @see __construct()
|
||||
*/
|
||||
function Mail_smtpmx($params)
|
||||
{
|
||||
$this->__construct($params);
|
||||
register_shutdown_function(array(&$this, '__destruct'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor implementation to ensure that we disconnect from any
|
||||
* potentially-alive persistent SMTP connections.
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if (is_object($this->_smtp)) {
|
||||
$this->_smtp->disconnect();
|
||||
$this->_smtp = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Mail::send() function using SMTP direct delivery
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $recipients in RFC822 style or array
|
||||
* @param array $headers The array of headers to send with the mail.
|
||||
* @param string $body The full text of the message body,
|
||||
* @return mixed Returns true on success, or a PEAR_Error
|
||||
*/
|
||||
function send($recipients, $headers, $body)
|
||||
{
|
||||
if (!is_array($headers)) {
|
||||
return PEAR::raiseError('$headers must be an array');
|
||||
}
|
||||
|
||||
$result = $this->_sanitizeHeaders($headers);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Prepare headers
|
||||
$headerElements = $this->prepareHeaders($headers);
|
||||
if (is_a($headerElements, 'PEAR_Error')) {
|
||||
return $headerElements;
|
||||
}
|
||||
list($from, $textHeaders) = $headerElements;
|
||||
|
||||
// use 'Return-Path' if possible
|
||||
if (!empty($headers['Return-Path'])) {
|
||||
$from = $headers['Return-Path'];
|
||||
}
|
||||
if (!isset($from)) {
|
||||
return $this->_raiseError('no_from');
|
||||
}
|
||||
|
||||
// Prepare recipients
|
||||
$recipients = $this->parseRecipients($recipients);
|
||||
if (is_a($recipients, 'PEAR_Error')) {
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
foreach ($recipients as $rcpt) {
|
||||
list($user, $host) = explode('@', $rcpt);
|
||||
|
||||
$mx = $this->_getMx($host);
|
||||
if (is_a($mx, 'PEAR_Error')) {
|
||||
return $mx;
|
||||
}
|
||||
|
||||
if (empty($mx)) {
|
||||
$info = array('rcpt' => $rcpt);
|
||||
return $this->_raiseError('no_mx', $info);
|
||||
}
|
||||
|
||||
$connected = false;
|
||||
foreach ($mx as $mserver => $mpriority) {
|
||||
$this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
|
||||
|
||||
// configure the SMTP connection.
|
||||
if ($this->debug) {
|
||||
$this->_smtp->setDebug(true);
|
||||
}
|
||||
|
||||
// attempt to connect to the configured SMTP server.
|
||||
$res = $this->_smtp->connect($this->timeout);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$this->_smtp = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// connection established
|
||||
if ($res) {
|
||||
$connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$connected) {
|
||||
$info = array(
|
||||
'host' => implode(', ', array_keys($mx)),
|
||||
'port' => $this->port,
|
||||
'rcpt' => $rcpt,
|
||||
);
|
||||
return $this->_raiseError('not_connected', $info);
|
||||
}
|
||||
|
||||
// Verify recipient
|
||||
if ($this->vrfy) {
|
||||
$res = $this->_smtp->vrfy($rcpt);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$info = array('rcpt' => $rcpt);
|
||||
return $this->_raiseError('failed_vrfy_rcpt', $info);
|
||||
}
|
||||
}
|
||||
|
||||
// mail from:
|
||||
$args['verp'] = $this->verp;
|
||||
$res = $this->_smtp->mailFrom($from, $args);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$info = array('from' => $from);
|
||||
return $this->_raiseError('failed_set_from', $info);
|
||||
}
|
||||
|
||||
// rcpt to:
|
||||
$res = $this->_smtp->rcptTo($rcpt);
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$info = array('rcpt' => $rcpt);
|
||||
return $this->_raiseError('failed_set_rcpt', $info);
|
||||
}
|
||||
|
||||
// Don't send anything in test mode
|
||||
if ($this->test) {
|
||||
$result = $this->_smtp->rset();
|
||||
$res = $this->_smtp->rset();
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
return $this->_raiseError('failed_rset');
|
||||
}
|
||||
|
||||
$this->_smtp->disconnect();
|
||||
$this->_smtp = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send data
|
||||
$res = $this->_smtp->data("$textHeaders\r\n$body");
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
$info = array('rcpt' => $rcpt);
|
||||
return $this->_raiseError('failed_send_data', $info);
|
||||
}
|
||||
|
||||
$this->_smtp->disconnect();
|
||||
$this->_smtp = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recieve mx rexords for a spciefied host
|
||||
*
|
||||
* The MX records
|
||||
*
|
||||
* @access private
|
||||
* @param string $host mail host
|
||||
* @return mixed sorted
|
||||
*/
|
||||
function _getMx($host)
|
||||
{
|
||||
$mx = array();
|
||||
|
||||
if ($this->withNetDns) {
|
||||
$res = $this->_loadNetDns();
|
||||
if (is_a($res, 'PEAR_Error')) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
$response = $this->resolver->query($host, 'MX');
|
||||
if (!$response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($response->answer as $rr) {
|
||||
if ($rr->type == 'MX') {
|
||||
$mx[$rr->exchange] = $rr->preference;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$mxHost = array();
|
||||
$mxWeight = array();
|
||||
|
||||
if (!getmxrr($host, $mxHost, $mxWeight)) {
|
||||
return false;
|
||||
}
|
||||
for ($i = 0; $i < count($mxHost); ++$i) {
|
||||
$mx[$mxHost[$i]] = $mxWeight[$i];
|
||||
}
|
||||
}
|
||||
|
||||
asort($mx);
|
||||
return $mx;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize PEAR:Net_DNS_Resolver
|
||||
*
|
||||
* @access private
|
||||
* @return boolean true on success
|
||||
*/
|
||||
function _loadNetDns()
|
||||
{
|
||||
if (is_object($this->resolver)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!include_once 'Net/DNS.php') {
|
||||
return $this->_raiseError('no_resolver');
|
||||
}
|
||||
|
||||
$this->resolver = new Net_DNS_Resolver();
|
||||
if ($this->debug) {
|
||||
$this->resolver->test = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* raise standardized error
|
||||
*
|
||||
* include additional information in error message
|
||||
*
|
||||
* @access private
|
||||
* @param string $id maps error ids to codes and message
|
||||
* @param array $info optional information in associative array
|
||||
* @see _errorCode
|
||||
*/
|
||||
function _raiseError($id, $info = array())
|
||||
{
|
||||
$code = $this->errorCode[$id]['code'];
|
||||
$msg = $this->errorCode[$id]['msg'];
|
||||
|
||||
// include info to messages
|
||||
if (!empty($info)) {
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
foreach ($info as $key => $value) {
|
||||
array_push($search, '{' . strtoupper($key) . '}');
|
||||
array_push($replace, $value);
|
||||
}
|
||||
|
||||
$msg = str_replace($search, $replace, $msg);
|
||||
}
|
||||
|
||||
return PEAR::raiseError($msg, $code);
|
||||
}
|
||||
|
||||
}
|
1082
extlib/Net/SMTP.php
Normal file
1082
extlib/Net/SMTP.php
Normal file
File diff suppressed because it is too large
Load Diff
755
extlib/OAuth.php
Normal file
755
extlib/OAuth.php
Normal file
@ -0,0 +1,755 @@
|
||||
<?php
|
||||
// vim: foldmethod=marker
|
||||
|
||||
/* Generic exception class
|
||||
*/
|
||||
class OAuthException extends Exception {/*{{{*/
|
||||
// pass
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthConsumer {/*{{{*/
|
||||
public $key;
|
||||
public $secret;
|
||||
|
||||
function __construct($key, $secret, $callback_url=NULL) {/*{{{*/
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
$this->callback_url = $callback_url;
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthToken {/*{{{*/
|
||||
// access tokens and request tokens
|
||||
public $key;
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* key = the token
|
||||
* secret = the token secret
|
||||
*/
|
||||
function __construct($key, $secret) {/*{{{*/
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* generates the basic string serialization of a token that a server
|
||||
* would respond to request_token and access_token calls with
|
||||
*/
|
||||
function to_string() {/*{{{*/
|
||||
return "oauth_token=" . OAuthUtil::urlencodeRFC3986($this->key) .
|
||||
"&oauth_token_secret=" . OAuthUtil::urlencodeRFC3986($this->secret);
|
||||
}/*}}}*/
|
||||
|
||||
function __toString() {/*{{{*/
|
||||
return $this->to_string();
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthSignatureMethod {/*{{{*/
|
||||
public function check_signature(&$request, $consumer, $token, $signature) {
|
||||
$built = $this->build_signature($request, $consumer, $token);
|
||||
return $built == $signature;
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/
|
||||
function get_name() {/*{{{*/
|
||||
return "HMAC-SHA1";
|
||||
}/*}}}*/
|
||||
|
||||
public function build_signature($request, $consumer, $token) {/*{{{*/
|
||||
$base_string = $request->get_signature_base_string();
|
||||
$request->base_string = $base_string;
|
||||
|
||||
$key_parts = array(
|
||||
$consumer->secret,
|
||||
($token) ? $token->secret : ""
|
||||
);
|
||||
|
||||
$key_parts = array_map(array('OAuthUtil','urlencodeRFC3986'), $key_parts);
|
||||
$key = implode('&', $key_parts);
|
||||
|
||||
return base64_encode( hash_hmac('sha1', $base_string, $key, true));
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/
|
||||
public function get_name() {/*{{{*/
|
||||
return "PLAINTEXT";
|
||||
}/*}}}*/
|
||||
|
||||
public function build_signature($request, $consumer, $token) {/*{{{*/
|
||||
$sig = array(
|
||||
OAuthUtil::urlencodeRFC3986($consumer->secret)
|
||||
);
|
||||
|
||||
if ($token) {
|
||||
array_push($sig, OAuthUtil::urlencodeRFC3986($token->secret));
|
||||
} else {
|
||||
array_push($sig, '');
|
||||
}
|
||||
|
||||
$raw = implode("&", $sig);
|
||||
// for debug purposes
|
||||
$request->base_string = $raw;
|
||||
|
||||
return OAuthUtil::urlencodeRFC3986($raw);
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/
|
||||
public function get_name() {/*{{{*/
|
||||
return "RSA-SHA1";
|
||||
}/*}}}*/
|
||||
|
||||
protected function fetch_public_cert(&$request) {/*{{{*/
|
||||
// not implemented yet, ideas are:
|
||||
// (1) do a lookup in a table of trusted certs keyed off of consumer
|
||||
// (2) fetch via http using a url provided by the requester
|
||||
// (3) some sort of specific discovery code based on request
|
||||
//
|
||||
// either way should return a string representation of the certificate
|
||||
throw Exception("fetch_public_cert not implemented");
|
||||
}/*}}}*/
|
||||
|
||||
protected function fetch_private_cert(&$request) {/*{{{*/
|
||||
// not implemented yet, ideas are:
|
||||
// (1) do a lookup in a table of trusted certs keyed off of consumer
|
||||
//
|
||||
// either way should return a string representation of the certificate
|
||||
throw Exception("fetch_private_cert not implemented");
|
||||
}/*}}}*/
|
||||
|
||||
public function build_signature(&$request, $consumer, $token) {/*{{{*/
|
||||
$base_string = $request->get_signature_base_string();
|
||||
$request->base_string = $base_string;
|
||||
|
||||
// Fetch the private key cert based on the request
|
||||
$cert = $this->fetch_private_cert($request);
|
||||
|
||||
// Pull the private key ID from the certificate
|
||||
$privatekeyid = openssl_get_privatekey($cert);
|
||||
|
||||
// Sign using the key
|
||||
$ok = openssl_sign($base_string, $signature, $privatekeyid);
|
||||
|
||||
// Release the key resource
|
||||
openssl_free_key($privatekeyid);
|
||||
|
||||
return base64_encode($signature);
|
||||
} /*}}}*/
|
||||
|
||||
public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/
|
||||
$decoded_sig = base64_decode($signature);
|
||||
|
||||
$base_string = $request->get_signature_base_string();
|
||||
|
||||
// Fetch the public key cert based on the request
|
||||
$cert = $this->fetch_public_cert($request);
|
||||
|
||||
// Pull the public key ID from the certificate
|
||||
$publickeyid = openssl_get_publickey($cert);
|
||||
|
||||
// Check the computed signature against the one passed in the query
|
||||
$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
|
||||
|
||||
// Release the key resource
|
||||
openssl_free_key($publickeyid);
|
||||
|
||||
return $ok == 1;
|
||||
} /*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthRequest {/*{{{*/
|
||||
private $parameters;
|
||||
private $http_method;
|
||||
private $http_url;
|
||||
// for debug purposes
|
||||
public $base_string;
|
||||
public static $version = '1.0';
|
||||
|
||||
function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/
|
||||
@$parameters or $parameters = array();
|
||||
$this->parameters = $parameters;
|
||||
$this->http_method = $http_method;
|
||||
$this->http_url = $http_url;
|
||||
}/*}}}*/
|
||||
|
||||
|
||||
/**
|
||||
* attempt to build up a request from what was passed to the server
|
||||
*/
|
||||
public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/
|
||||
$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https';
|
||||
@$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
@$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
$request_headers = OAuthRequest::get_headers();
|
||||
|
||||
// let the library user override things however they'd like, if they know
|
||||
// which parameters to use then go for it, for example XMLRPC might want to
|
||||
// do this
|
||||
if ($parameters) {
|
||||
$req = new OAuthRequest($http_method, $http_url, $parameters);
|
||||
}
|
||||
// next check for the auth header, we need to do some extra stuff
|
||||
// if that is the case, namely suck in the parameters from GET or POST
|
||||
// so that we can include them in the signature
|
||||
else if (@substr($request_headers['Authorization'], 0, 5) == "OAuth") {
|
||||
$header_parameters = OAuthRequest::split_header($request_headers['Authorization']);
|
||||
if ($http_method == "GET") {
|
||||
$req_parameters = $_GET;
|
||||
}
|
||||
else if ($http_method == "POST") {
|
||||
$req_parameters = $_POST;
|
||||
}
|
||||
$parameters = array_merge($header_parameters, $req_parameters);
|
||||
$req = new OAuthRequest($http_method, $http_url, $parameters);
|
||||
}
|
||||
else if ($http_method == "GET") {
|
||||
$req = new OAuthRequest($http_method, $http_url, $_GET);
|
||||
}
|
||||
else if ($http_method == "POST") {
|
||||
$req = new OAuthRequest($http_method, $http_url, $_POST);
|
||||
}
|
||||
return $req;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* pretty much a helper function to set up the request
|
||||
*/
|
||||
public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/
|
||||
@$parameters or $parameters = array();
|
||||
$defaults = array("oauth_version" => OAuthRequest::$version,
|
||||
"oauth_nonce" => OAuthRequest::generate_nonce(),
|
||||
"oauth_timestamp" => OAuthRequest::generate_timestamp(),
|
||||
"oauth_consumer_key" => $consumer->key);
|
||||
$parameters = array_merge($defaults, $parameters);
|
||||
|
||||
if ($token) {
|
||||
$parameters['oauth_token'] = $token->key;
|
||||
}
|
||||
return new OAuthRequest($http_method, $http_url, $parameters);
|
||||
}/*}}}*/
|
||||
|
||||
public function set_parameter($name, $value) {/*{{{*/
|
||||
$this->parameters[$name] = $value;
|
||||
}/*}}}*/
|
||||
|
||||
public function get_parameter($name) {/*{{{*/
|
||||
return $this->parameters[$name];
|
||||
}/*}}}*/
|
||||
|
||||
public function get_parameters() {/*{{{*/
|
||||
return $this->parameters;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* Returns the normalized parameters of the request
|
||||
*
|
||||
* This will be all (except oauth_signature) parameters,
|
||||
* sorted first by key, and if duplicate keys, then by
|
||||
* value.
|
||||
*
|
||||
* The returned string will be all the key=value pairs
|
||||
* concated by &.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_signable_parameters() {/*{{{*/
|
||||
// Grab all parameters
|
||||
$params = $this->parameters;
|
||||
|
||||
// Remove oauth_signature if present
|
||||
if (isset($params['oauth_signature'])) {
|
||||
unset($params['oauth_signature']);
|
||||
}
|
||||
|
||||
// Urlencode both keys and values
|
||||
$keys = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_keys($params));
|
||||
$values = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_values($params));
|
||||
$params = array_combine($keys, $values);
|
||||
|
||||
// Sort by keys (natsort)
|
||||
uksort($params, 'strnatcmp');
|
||||
|
||||
// Generate key=value pairs
|
||||
$pairs = array();
|
||||
foreach ($params as $key=>$value ) {
|
||||
if (is_array($value)) {
|
||||
// If the value is an array, it's because there are multiple
|
||||
// with the same key, sort them, then add all the pairs
|
||||
natsort($value);
|
||||
foreach ($value as $v2) {
|
||||
$pairs[] = $key . '=' . $v2;
|
||||
}
|
||||
} else {
|
||||
$pairs[] = $key . '=' . $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the pairs, concated with &
|
||||
return implode('&', $pairs);
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* Returns the base string of this request
|
||||
*
|
||||
* The base string defined as the method, the url
|
||||
* and the parameters (normalized), each urlencoded
|
||||
* and the concated with &.
|
||||
*/
|
||||
public function get_signature_base_string() {/*{{{*/
|
||||
$parts = array(
|
||||
$this->get_normalized_http_method(),
|
||||
$this->get_normalized_http_url(),
|
||||
$this->get_signable_parameters()
|
||||
);
|
||||
|
||||
$parts = array_map(array('OAuthUtil', 'urlencodeRFC3986'), $parts);
|
||||
|
||||
return implode('&', $parts);
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* just uppercases the http method
|
||||
*/
|
||||
public function get_normalized_http_method() {/*{{{*/
|
||||
return strtoupper($this->http_method);
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* parses the url and rebuilds it to be
|
||||
* scheme://host/path
|
||||
*/
|
||||
public function get_normalized_http_url() {/*{{{*/
|
||||
$parts = parse_url($this->http_url);
|
||||
|
||||
$port = @$parts['port'];
|
||||
$scheme = $parts['scheme'];
|
||||
$host = $parts['host'];
|
||||
$path = @$parts['path'];
|
||||
|
||||
$port or $port = ($scheme == 'https') ? '443' : '80';
|
||||
|
||||
if (($scheme == 'https' && $port != '443')
|
||||
|| ($scheme == 'http' && $port != '80')) {
|
||||
$host = "$host:$port";
|
||||
}
|
||||
return "$scheme://$host$path";
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* builds a url usable for a GET request
|
||||
*/
|
||||
public function to_url() {/*{{{*/
|
||||
$out = $this->get_normalized_http_url() . "?";
|
||||
$out .= $this->to_postdata();
|
||||
return $out;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* builds the data one would send in a POST request
|
||||
*/
|
||||
public function to_postdata() {/*{{{*/
|
||||
$total = array();
|
||||
foreach ($this->parameters as $k => $v) {
|
||||
$total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v);
|
||||
}
|
||||
$out = implode("&", $total);
|
||||
return $out;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* builds the Authorization: header
|
||||
*/
|
||||
public function to_header($realm="") {/*{{{*/
|
||||
$out ='"Authorization: OAuth realm="' . $realm . '",';
|
||||
$total = array();
|
||||
foreach ($this->parameters as $k => $v) {
|
||||
if (substr($k, 0, 5) != "oauth") continue;
|
||||
$out .= ',' . OAuthUtil::urlencodeRFC3986($k) . '="' . OAuthUtil::urlencodeRFC3986($v) . '"';
|
||||
}
|
||||
return $out;
|
||||
}/*}}}*/
|
||||
|
||||
public function __toString() {/*{{{*/
|
||||
return $this->to_url();
|
||||
}/*}}}*/
|
||||
|
||||
|
||||
public function sign_request($signature_method, $consumer, $token) {/*{{{*/
|
||||
$this->set_parameter("oauth_signature_method", $signature_method->get_name());
|
||||
$signature = $this->build_signature($signature_method, $consumer, $token);
|
||||
$this->set_parameter("oauth_signature", $signature);
|
||||
}/*}}}*/
|
||||
|
||||
public function build_signature($signature_method, $consumer, $token) {/*{{{*/
|
||||
$signature = $signature_method->build_signature($this, $consumer, $token);
|
||||
return $signature;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* util function: current timestamp
|
||||
*/
|
||||
private static function generate_timestamp() {/*{{{*/
|
||||
return time();
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* util function: current nonce
|
||||
*/
|
||||
private static function generate_nonce() {/*{{{*/
|
||||
$mt = microtime();
|
||||
$rand = mt_rand();
|
||||
|
||||
return md5($mt . $rand); // md5s look nicer than numbers
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* util function for turning the Authorization: header into
|
||||
* parameters, has to do some unescaping
|
||||
*/
|
||||
private static function split_header($header) {/*{{{*/
|
||||
// remove 'OAuth ' at the start of a header
|
||||
$header = substr($header, 6);
|
||||
|
||||
// error cases: commas in parameter values?
|
||||
$parts = explode(",", $header);
|
||||
$out = array();
|
||||
foreach ($parts as $param) {
|
||||
$param = ltrim($param);
|
||||
// skip the "realm" param, nobody ever uses it anyway
|
||||
if (substr($param, 0, 5) != "oauth") continue;
|
||||
|
||||
$param_parts = explode("=", $param);
|
||||
|
||||
// rawurldecode() used because urldecode() will turn a "+" in the
|
||||
// value into a space
|
||||
$out[$param_parts[0]] = rawurldecode(substr($param_parts[1], 1, -1));
|
||||
}
|
||||
return $out;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* helper to try to sort out headers for people who aren't running apache
|
||||
*/
|
||||
private static function get_headers() {/*{{{*/
|
||||
if (function_exists('apache_request_headers')) {
|
||||
// we need this to get the actual Authorization: header
|
||||
// because apache tends to tell us it doesn't exist
|
||||
return apache_request_headers();
|
||||
}
|
||||
// otherwise we don't have apache and are just going to have to hope
|
||||
// that $_SERVER actually contains what we need
|
||||
$out = array();
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (substr($key, 0, 5) == "HTTP_") {
|
||||
// this is chaos, basically it is just there to capitalize the first
|
||||
// letter of every word that is not an initial HTTP and strip HTTP
|
||||
// code from przemek
|
||||
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
|
||||
$out[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthServer {/*{{{*/
|
||||
protected $timestamp_threshold = 300; // in seconds, five minutes
|
||||
protected $version = 1.0; // hi blaine
|
||||
protected $signature_methods = array();
|
||||
|
||||
protected $data_store;
|
||||
|
||||
function __construct($data_store) {/*{{{*/
|
||||
$this->data_store = $data_store;
|
||||
}/*}}}*/
|
||||
|
||||
public function add_signature_method($signature_method) {/*{{{*/
|
||||
$this->signature_methods[$signature_method->get_name()] =
|
||||
$signature_method;
|
||||
}/*}}}*/
|
||||
|
||||
// high level functions
|
||||
|
||||
/**
|
||||
* process a request_token request
|
||||
* returns the request token on success
|
||||
*/
|
||||
public function fetch_request_token(&$request) {/*{{{*/
|
||||
$this->get_version($request);
|
||||
|
||||
$consumer = $this->get_consumer($request);
|
||||
|
||||
// no token required for the initial token request
|
||||
$token = NULL;
|
||||
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
|
||||
$new_token = $this->data_store->new_request_token($consumer);
|
||||
|
||||
return $new_token;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* process an access_token request
|
||||
* returns the access token on success
|
||||
*/
|
||||
public function fetch_access_token(&$request) {/*{{{*/
|
||||
$this->get_version($request);
|
||||
|
||||
$consumer = $this->get_consumer($request);
|
||||
|
||||
// requires authorized request token
|
||||
$token = $this->get_token($request, $consumer, "request");
|
||||
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
|
||||
$new_token = $this->data_store->new_access_token($token, $consumer);
|
||||
|
||||
return $new_token;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* verify an api call, checks all the parameters
|
||||
*/
|
||||
public function verify_request(&$request) {/*{{{*/
|
||||
$this->get_version($request);
|
||||
$consumer = $this->get_consumer($request);
|
||||
$token = $this->get_token($request, $consumer, "access");
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
return array($consumer, $token);
|
||||
}/*}}}*/
|
||||
|
||||
// Internals from here
|
||||
/**
|
||||
* version 1
|
||||
*/
|
||||
private function get_version(&$request) {/*{{{*/
|
||||
$version = $request->get_parameter("oauth_version");
|
||||
if (!$version) {
|
||||
$version = 1.0;
|
||||
}
|
||||
if ($version && $version != $this->version) {
|
||||
throw new OAuthException("OAuth version '$version' not supported");
|
||||
}
|
||||
return $version;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* figure out the signature with some defaults
|
||||
*/
|
||||
private function get_signature_method(&$request) {/*{{{*/
|
||||
$signature_method =
|
||||
@$request->get_parameter("oauth_signature_method");
|
||||
if (!$signature_method) {
|
||||
$signature_method = "PLAINTEXT";
|
||||
}
|
||||
if (!in_array($signature_method,
|
||||
array_keys($this->signature_methods))) {
|
||||
throw new OAuthException(
|
||||
"Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods))
|
||||
);
|
||||
}
|
||||
return $this->signature_methods[$signature_method];
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* try to find the consumer for the provided request's consumer key
|
||||
*/
|
||||
private function get_consumer(&$request) {/*{{{*/
|
||||
$consumer_key = @$request->get_parameter("oauth_consumer_key");
|
||||
if (!$consumer_key) {
|
||||
throw new OAuthException("Invalid consumer key");
|
||||
}
|
||||
|
||||
$consumer = $this->data_store->lookup_consumer($consumer_key);
|
||||
if (!$consumer) {
|
||||
throw new OAuthException("Invalid consumer");
|
||||
}
|
||||
|
||||
return $consumer;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* try to find the token for the provided request's token key
|
||||
*/
|
||||
private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/
|
||||
$token_field = @$request->get_parameter('oauth_token');
|
||||
$token = $this->data_store->lookup_token(
|
||||
$consumer, $token_type, $token_field
|
||||
);
|
||||
if (!$token) {
|
||||
throw new OAuthException("Invalid $token_type token: $token_field");
|
||||
}
|
||||
return $token;
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* all-in-one function to check the signature on a request
|
||||
* should guess the signature method appropriately
|
||||
*/
|
||||
private function check_signature(&$request, $consumer, $token) {/*{{{*/
|
||||
// this should probably be in a different method
|
||||
$timestamp = @$request->get_parameter('oauth_timestamp');
|
||||
$nonce = @$request->get_parameter('oauth_nonce');
|
||||
|
||||
$this->check_timestamp($timestamp);
|
||||
$this->check_nonce($consumer, $token, $nonce, $timestamp);
|
||||
|
||||
$signature_method = $this->get_signature_method($request);
|
||||
|
||||
$signature = $request->get_parameter('oauth_signature');
|
||||
$valid_sig = $signature_method->check_signature(
|
||||
$request,
|
||||
$consumer,
|
||||
$token,
|
||||
$signature
|
||||
);
|
||||
|
||||
if (!$valid_sig) {
|
||||
throw new OAuthException("Invalid signature");
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* check that the timestamp is new enough
|
||||
*/
|
||||
private function check_timestamp($timestamp) {/*{{{*/
|
||||
// verify that timestamp is recentish
|
||||
$now = time();
|
||||
if ($now - $timestamp > $this->timestamp_threshold) {
|
||||
throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
/**
|
||||
* check that the nonce is not repeated
|
||||
*/
|
||||
private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
|
||||
// verify that the nonce is uniqueish
|
||||
$found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
|
||||
if ($found) {
|
||||
throw new OAuthException("Nonce already used: $nonce");
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
|
||||
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthDataStore {/*{{{*/
|
||||
function lookup_consumer($consumer_key) {/*{{{*/
|
||||
// implement me
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {/*{{{*/
|
||||
// implement me
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
|
||||
// implement me
|
||||
}/*}}}*/
|
||||
|
||||
function fetch_request_token($consumer) {/*{{{*/
|
||||
// return a new token attached to this consumer
|
||||
}/*}}}*/
|
||||
|
||||
function fetch_access_token($token, $consumer) {/*{{{*/
|
||||
// return a new access token attached to this consumer
|
||||
// for the user associated with this token if the request token
|
||||
// is authorized
|
||||
// should also invalidate the request token
|
||||
}/*}}}*/
|
||||
|
||||
}/*}}}*/
|
||||
|
||||
|
||||
/* A very naive dbm-based oauth storage
|
||||
*/
|
||||
class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
|
||||
private $dbh;
|
||||
|
||||
function __construct($path = "oauth.gdbm") {/*{{{*/
|
||||
$this->dbh = dba_popen($path, 'c', 'gdbm');
|
||||
}/*}}}*/
|
||||
|
||||
function __destruct() {/*{{{*/
|
||||
dba_close($this->dbh);
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_consumer($consumer_key) {/*{{{*/
|
||||
$rv = dba_fetch("consumer_$consumer_key", $this->dbh);
|
||||
if ($rv === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
$obj = unserialize($rv);
|
||||
if (!($obj instanceof OAuthConsumer)) {
|
||||
return NULL;
|
||||
}
|
||||
return $obj;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {/*{{{*/
|
||||
$rv = dba_fetch("${token_type}_${token}", $this->dbh);
|
||||
if ($rv === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
$obj = unserialize($rv);
|
||||
if (!($obj instanceof OAuthToken)) {
|
||||
return NULL;
|
||||
}
|
||||
return $obj;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
|
||||
if (dba_exists("nonce_$nonce", $this->dbh)) {
|
||||
return TRUE;
|
||||
} else {
|
||||
dba_insert("nonce_$nonce", "1", $this->dbh);
|
||||
return FALSE;
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
function new_token($consumer, $type="request") {/*{{{*/
|
||||
$key = md5(time());
|
||||
$secret = time() + time();
|
||||
$token = new OAuthToken($key, md5(md5($secret)));
|
||||
if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
|
||||
throw new OAuthException("doooom!");
|
||||
}
|
||||
return $token;
|
||||
}/*}}}*/
|
||||
|
||||
function new_request_token($consumer) {/*{{{*/
|
||||
return $this->new_token($consumer, "request");
|
||||
}/*}}}*/
|
||||
|
||||
function new_access_token($token, $consumer) {/*{{{*/
|
||||
|
||||
$token = $this->new_token($consumer, 'access');
|
||||
dba_delete("request_" . $token->key, $this->dbh);
|
||||
return $token;
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
class OAuthUtil {/*{{{*/
|
||||
public static function urlencodeRFC3986($string) {/*{{{*/
|
||||
return str_replace('+', ' ',
|
||||
str_replace('%7E', '~', rawurlencode($string)));
|
||||
|
||||
}/*}}}*/
|
||||
|
||||
|
||||
// This decode function isn't taking into consideration the above
|
||||
// modifications to the encoding process. However, this method doesn't
|
||||
// seem to be used anywhere so leaving it as is.
|
||||
public static function urldecodeRFC3986($string) {/*{{{*/
|
||||
return rawurldecode($string);
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
|
||||
?>
|
1118
extlib/PEAR.php
Normal file
1118
extlib/PEAR.php
Normal file
File diff suppressed because it is too large
Load Diff
39
extlib/XMPPHP/Exception.php
Normal file
39
extlib/XMPPHP/Exception.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/**
|
||||
* XMPPHP Exception
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
* @version $Id$
|
||||
*/
|
||||
class XMPPHP_Exception extends Exception {
|
||||
}
|
116
extlib/XMPPHP/Log.php
Normal file
116
extlib/XMPPHP/Log.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/**
|
||||
* XMPPHP Log
|
||||
*
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
* @version $Id$
|
||||
*/
|
||||
class XMPPHP_Log {
|
||||
|
||||
const LEVEL_ERROR = 0;
|
||||
const LEVEL_WARNING = 1;
|
||||
const LEVEL_INFO = 2;
|
||||
const LEVEL_DEBUG = 3;
|
||||
const LEVEL_VERBOSE = 4;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE');
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $runlevel;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $printout;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $printout
|
||||
* @param string $runlevel
|
||||
*/
|
||||
public function __construct($printout = false, $runlevel = self::LEVEL_INFO) {
|
||||
$this->printout = (boolean)$printout;
|
||||
$this->runlevel = (int)$runlevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message to the log data array
|
||||
* If printout in this instance is set to true, directly output the message
|
||||
*
|
||||
* @param string $msg
|
||||
* @param integer $runlevel
|
||||
*/
|
||||
public function log($msg, $runlevel = self::LEVEL_INFO) {
|
||||
$time = time();
|
||||
$this->data[] = array($this->runlevel, $msg, $time);
|
||||
if($this->printout and $runlevel <= $this->runlevel) {
|
||||
$this->writeLine($msg, $runlevel, $time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the complete log.
|
||||
* Log will be cleared if $clear = true
|
||||
*
|
||||
* @param boolean $clear
|
||||
* @param integer $runlevel
|
||||
*/
|
||||
public function printout($clear = true, $runlevel = null) {
|
||||
if($runlevel === null) {
|
||||
$runlevel = $this->runlevel;
|
||||
}
|
||||
foreach($this->data as $data) {
|
||||
if($runlevel <= $data[0]) {
|
||||
$this->writeLine($data[1], $runlevel, $data[2]);
|
||||
}
|
||||
}
|
||||
if($clear) {
|
||||
$this->data = array();
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeLine($msg, $runlevel, $time) {
|
||||
//echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n";
|
||||
echo $time." [".$this->names[$runlevel]."]: ".$msg."\n";
|
||||
}
|
||||
}
|
155
extlib/XMPPHP/XMLObj.php
Normal file
155
extlib/XMPPHP/XMLObj.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/**
|
||||
* XMPPHP XML Object
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
* @version $Id$
|
||||
*/
|
||||
class XMPPHP_XMLObj {
|
||||
/**
|
||||
* Tag name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Namespace
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $ns;
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $attrs = array();
|
||||
|
||||
/**
|
||||
* Subs?
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $subs = array();
|
||||
|
||||
/**
|
||||
* Node data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $data = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $ns
|
||||
* @param array $attrs
|
||||
* @param string $data
|
||||
*/
|
||||
public function __construct($name, $ns = '', $attrs = array(), $data = '') {
|
||||
$this->name = strtolower($name);
|
||||
$this->ns = $ns;
|
||||
if(is_array($attrs) && count($attrs)) {
|
||||
foreach($attrs as $key => $value) {
|
||||
$this->attrs[strtolower($key)] = $value;
|
||||
}
|
||||
}
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump this XML Object to output.
|
||||
*
|
||||
* @param integer $depth
|
||||
*/
|
||||
public function printObj($depth = 0) {
|
||||
print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data;
|
||||
print "\n";
|
||||
foreach($this->subs as $sub) {
|
||||
$sub->printObj($depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this XML Object in xml notation
|
||||
*
|
||||
* @param string $str
|
||||
*/
|
||||
public function toString($str = '') {
|
||||
$str .= "<{$this->name} xmlns='{$this->ns}' ";
|
||||
foreach($this->attrs as $key => $value) {
|
||||
if($key != 'xmlns') {
|
||||
$value = htmlspecialchars($value);
|
||||
$str .= "$key='$value' ";
|
||||
}
|
||||
}
|
||||
$str .= ">";
|
||||
foreach($this->subs as $sub) {
|
||||
$str .= $sub->toString();
|
||||
}
|
||||
$body = htmlspecialchars($this->data);
|
||||
$str .= "$body</{$this->name}>";
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this XML Object the given sub?
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasSub($name) {
|
||||
foreach($this->subs as $sub) {
|
||||
if($sub->name == $name) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sub
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $attrs
|
||||
* @param string $ns
|
||||
*/
|
||||
public function sub($name, $attrs = null, $ns = null) {
|
||||
foreach($this->subs as $sub) {
|
||||
if($sub->name == $name) {
|
||||
return $sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
619
extlib/XMPPHP/XMLStream.php
Normal file
619
extlib/XMPPHP/XMLStream.php
Normal file
@ -0,0 +1,619 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/** XMPPHP_Exception */
|
||||
require_once 'Exception.php';
|
||||
|
||||
/** XMPPHP_XMLObj */
|
||||
require_once 'XMLObj.php';
|
||||
|
||||
/** XMPPHP_Log */
|
||||
require_once 'Log.php';
|
||||
|
||||
/**
|
||||
* XMPPHP XML Stream
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
* @version $Id$
|
||||
*/
|
||||
class XMPPHP_XMLStream {
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $socket;
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $parser;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $buffer;
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $xml_depth = 0;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $host;
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $port;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $stream_start = '<stream>';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $stream_end = '</stream>';
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $disconnected = false;
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $sent_disconnect = false;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $ns_map = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $current_ns = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $xmlobj = null;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $nshandlers = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $idhandlers = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $eventhandlers = array();
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $lastid = 0;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $default_ns;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $until = '';
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $until_happened = false;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $until_payload = array();
|
||||
/**
|
||||
* @var XMPPHP_Log
|
||||
*/
|
||||
protected $log;
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $reconnect = true;
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $been_reset = false;
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $is_server;
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $last_send = 0;
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $use_ssl = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $host
|
||||
* @param string $port
|
||||
* @param boolean $printlog
|
||||
* @param string $loglevel
|
||||
* @param boolean $is_server
|
||||
*/
|
||||
public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) {
|
||||
$this->reconnect = !$is_server;
|
||||
$this->is_server = $is_server;
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->setupParser();
|
||||
$this->log = new XMPPHP_Log($printlog, $loglevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
* Cleanup connection
|
||||
*/
|
||||
public function __destruct() {
|
||||
if(!$this->disconnected && $this->socket) {
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the log instance
|
||||
*
|
||||
* @return XMPPHP_Log
|
||||
*/
|
||||
public function getLog() {
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next ID
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getId() {
|
||||
$this->lastid++;
|
||||
return $this->lastid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SSL
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function useSSL($use=true) {
|
||||
$this->use_ssl = $use;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ID Handler
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $pointer
|
||||
* @param string $obj
|
||||
*/
|
||||
public function addIdHandler($id, $pointer, $obj = null) {
|
||||
$this->idhandlers[$id] = array($pointer, $obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Handler
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $ns
|
||||
* @param string $pointer
|
||||
* @param string $obj
|
||||
* @param integer $depth
|
||||
*/
|
||||
public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) {
|
||||
$this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Evemt Handler
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $pointer
|
||||
* @param string $obj
|
||||
*/
|
||||
public function addEventHandler($name, $pointer, $obj) {
|
||||
$this->eventhanders[] = array($name, $pointer, $obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to XMPP Host
|
||||
*
|
||||
* @param integer $timeout
|
||||
* @param boolean $persistent
|
||||
* @param boolean $sendinit
|
||||
*/
|
||||
public function connect($timeout = 30, $persistent = false, $sendinit = true) {
|
||||
$this->disconnected = false;
|
||||
$this->sent_disconnect = false;
|
||||
if($persistent) {
|
||||
$conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
|
||||
} else {
|
||||
$conflag = STREAM_CLIENT_CONNECT;
|
||||
}
|
||||
$conntype = 'tcp';
|
||||
if($this->use_ssl) $conntype = 'ssl';
|
||||
$this->log->log("Connecting to $conntype://{$this->host}:{$this->port}");
|
||||
try {
|
||||
$this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag);
|
||||
} catch (Exception $e) {
|
||||
throw new XMPPHP_Exception($e->getMessage());
|
||||
}
|
||||
if(!$this->socket) {
|
||||
$this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR);
|
||||
$this->disconnected = true;
|
||||
|
||||
throw new XMPPHP_Exception('Could not connect.');
|
||||
}
|
||||
stream_set_blocking($this->socket, 1);
|
||||
if($sendinit) $this->send($this->stream_start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect XMPP Host
|
||||
*/
|
||||
public function doReconnect() {
|
||||
if(!$this->is_server) {
|
||||
$this->log->log("Reconnecting...", XMPPHP_Log::LEVEL_WARNING);
|
||||
$this->connect(30, false, false);
|
||||
$this->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from XMPP Host
|
||||
*/
|
||||
public function disconnect() {
|
||||
$this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE);
|
||||
$this->reconnect = false;
|
||||
$this->send($this->stream_end);
|
||||
$this->sent_disconnect = true;
|
||||
$this->processUntil('end_stream', 5);
|
||||
$this->disconnected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we are disconnected?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isDisconnected() {
|
||||
return $this->disconnected;
|
||||
}
|
||||
|
||||
private function __process() {
|
||||
$read = array($this->socket);
|
||||
$write = null;
|
||||
$except = null;
|
||||
$updated = @stream_select($read, $write, $except, 1);
|
||||
if ($updated > 0) {
|
||||
$buff = @fread($this->socket, 1024);
|
||||
if(!$buff) {
|
||||
if($this->reconnect) {
|
||||
$this->doReconnect();
|
||||
} else {
|
||||
fclose($this->socket);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE);
|
||||
xml_parse($this->parser, $buff, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process() {
|
||||
$updated = '';
|
||||
while(!$this->disconnect) {
|
||||
$this->__process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process until a timeout occurs
|
||||
*
|
||||
* @param integer $timeout
|
||||
* @return string
|
||||
*/
|
||||
public function processTime($timeout = -1) {
|
||||
$start = time();
|
||||
$updated = '';
|
||||
while(!$this->disconnected and ($timeout == -1 or time() - $start < $timeout)) {
|
||||
$this->__process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process until a specified event or a timeout occurs
|
||||
*
|
||||
* @param string|array $event
|
||||
* @param integer $timeout
|
||||
* @return string
|
||||
*/
|
||||
public function processUntil($event, $timeout=-1) {
|
||||
$start = time();
|
||||
if(!is_array($event)) $event = array($event);
|
||||
$this->until[] = $event;
|
||||
end($this->until);
|
||||
$event_key = key($this->until);
|
||||
reset($this->until);
|
||||
$updated = '';
|
||||
while(!$this->disconnected and $this->until[$event_key] and (time() - $start < $timeout or $timeout == -1)) {
|
||||
$this->__process();
|
||||
}
|
||||
if(array_key_exists($event_key, $this->until_payload)) {
|
||||
$payload = $this->until_payload[$event_key];
|
||||
} else {
|
||||
$payload = array();
|
||||
}
|
||||
unset($this->until_payload[$event_key]);
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obsolete?
|
||||
*/
|
||||
public function Xapply_socket($socket) {
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML start callback
|
||||
*
|
||||
* @see xml_set_element_handler
|
||||
*
|
||||
* @param resource $parser
|
||||
* @param string $name
|
||||
*/
|
||||
public function startXML($parser, $name, $attr) {
|
||||
if($this->been_reset) {
|
||||
$this->been_reset = false;
|
||||
$this->xml_depth = 0;
|
||||
}
|
||||
$this->xml_depth++;
|
||||
if(array_key_exists('XMLNS', $attr)) {
|
||||
$this->current_ns[$this->xml_depth] = $attr['XMLNS'];
|
||||
} else {
|
||||
$this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
|
||||
if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns;
|
||||
}
|
||||
$ns = $this->current_ns[$this->xml_depth];
|
||||
foreach($attr as $key => $value) {
|
||||
if(strstr($key, ":")) {
|
||||
$key = explode(':', $key);
|
||||
$key = $key[1];
|
||||
$this->ns_map[$key] = $value;
|
||||
}
|
||||
}
|
||||
if(!strstr($name, ":") === false)
|
||||
{
|
||||
$name = explode(':', $name);
|
||||
$ns = $this->ns_map[$name[0]];
|
||||
$name = $name[1];
|
||||
}
|
||||
$obj = new XMPPHP_XMLObj($name, $ns, $attr);
|
||||
if($this->xml_depth > 1) {
|
||||
$this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
|
||||
}
|
||||
$this->xmlobj[$this->xml_depth] = $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML end callback
|
||||
*
|
||||
* @see xml_set_element_handler
|
||||
*
|
||||
* @param resource $parser
|
||||
* @param string $name
|
||||
*/
|
||||
public function endXML($parser, $name) {
|
||||
#$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG);
|
||||
#print "$name\n";
|
||||
if($this->been_reset) {
|
||||
$this->been_reset = false;
|
||||
$this->xml_depth = 0;
|
||||
}
|
||||
$this->xml_depth--;
|
||||
if($this->xml_depth == 1) {
|
||||
#clean-up old objects
|
||||
$found = false;
|
||||
foreach($this->nshandlers as $handler) {
|
||||
if($handler[4] != 1 and $this->xmlobj[2]->hasSub($handler[0])) {
|
||||
$searchxml = $this->xmlobj[2]->sub($handler[0]);
|
||||
} elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) {
|
||||
$searchxml = $this->xmlobj[2];
|
||||
}
|
||||
if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) {
|
||||
if($handler[3] === null) $handler[3] = $this;
|
||||
$this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG);
|
||||
$handler[3]->$handler[2]($this->xmlobj[2]);
|
||||
}
|
||||
}
|
||||
foreach($this->idhandlers as $id => $handler) {
|
||||
if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
|
||||
if($handler[1] === null) $handler[1] = $this;
|
||||
$handler[1]->$handler[0]($this->xmlobj[2]);
|
||||
#id handlers are only used once
|
||||
unset($this->idhandlers[$id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(is_array($this->xmlobj)) {
|
||||
$this->xmlobj = array_slice($this->xmlobj, 0, 1);
|
||||
if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) {
|
||||
$this->xmlobj[0]->subs = null;
|
||||
}
|
||||
}
|
||||
unset($this->xmlobj[2]);
|
||||
}
|
||||
if($this->xml_depth == 0 and !$this->been_reset) {
|
||||
if(!$this->disconnected) {
|
||||
if(!$this->sent_disconnect) {
|
||||
$this->send($this->stream_end);
|
||||
}
|
||||
$this->disconnected = true;
|
||||
$this->sent_disconnect = true;
|
||||
fclose($this->socket);
|
||||
if($this->reconnect) {
|
||||
$this->doReconnect();
|
||||
}
|
||||
}
|
||||
$this->event('end_stream');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* XML character callback
|
||||
* @see xml_set_character_data_handler
|
||||
*
|
||||
* @param resource $parser
|
||||
* @param string $data
|
||||
*/
|
||||
public function charXML($parser, $data) {
|
||||
if(array_key_exists($this->xml_depth, $this->xmlobj)) {
|
||||
$this->xmlobj[$this->xml_depth]->data .= $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event?
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $payload
|
||||
*/
|
||||
public function event($name, $payload = null) {
|
||||
$this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG);
|
||||
foreach($this->eventhandlers as $handler) {
|
||||
if($name == $handler[0]) {
|
||||
if($handler[2] === null) {
|
||||
$handler[2] = $this;
|
||||
}
|
||||
$handler[2]->$handler[1]($payload);
|
||||
}
|
||||
}
|
||||
foreach($this->until as $key => $until) {
|
||||
if(is_array($until)) {
|
||||
if(in_array($name, $until)) {
|
||||
$this->until_payload[$key][] = array($name, $payload);
|
||||
$this->until[$key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from socket
|
||||
*/
|
||||
public function read() {
|
||||
$buff = @fread($this->socket, 1024);
|
||||
if(!$buff) {
|
||||
if($this->reconnect) {
|
||||
$this->doReconnect();
|
||||
} else {
|
||||
fclose($this->socket);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE);
|
||||
xml_parse($this->parser, $buff, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to socket
|
||||
*
|
||||
* @param string $msg
|
||||
*/
|
||||
public function send($msg, $rec=false) {
|
||||
if($this->time() - $this->last_send < .1) {
|
||||
usleep(100000);
|
||||
}
|
||||
$wait = true;
|
||||
while($wait) {
|
||||
$read = null;
|
||||
$write = array($this->socket);
|
||||
$except = null;
|
||||
$select = @stream_select($read, $write, $except, 0, 0);
|
||||
if($select === False) {
|
||||
$this->doReconnect();
|
||||
return false;
|
||||
} elseif ($select > 0) {
|
||||
$wait = false;
|
||||
} else {
|
||||
usleep(100000);
|
||||
//$this->processTime(.25);
|
||||
}
|
||||
}
|
||||
$sentbytes = @fwrite($this->socket, $msg, 1024);
|
||||
$this->last_send = $this->time();
|
||||
$this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE);
|
||||
if($sentbytes === FALSE) {
|
||||
$this->doReconnect();
|
||||
} elseif ($sentbytes != mb_strlen($msg, '8bit')) {
|
||||
$this->send(mb_substr($msg, $sentbytes, mb_strlen($msg, '8bit'), '8bit'), true);
|
||||
}
|
||||
}
|
||||
|
||||
public function time() {
|
||||
list($usec, $sec) = explode(" ", microtime());
|
||||
return (float)$sec + (float)$usec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset connection
|
||||
*/
|
||||
public function reset() {
|
||||
$this->xml_depth = 0;
|
||||
unset($this->xmlobj);
|
||||
$this->xmlobj = array();
|
||||
$this->setupParser();
|
||||
if(!$this->is_server) {
|
||||
$this->send($this->stream_start);
|
||||
}
|
||||
$this->been_reset = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the XML parser
|
||||
*/
|
||||
public function setupParser() {
|
||||
$this->parser = xml_parser_create('UTF-8');
|
||||
xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
|
||||
xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
|
||||
xml_set_object($this->parser, $this);
|
||||
xml_set_element_handler($this->parser, 'startXML', 'endXML');
|
||||
xml_set_character_data_handler($this->parser, 'charXML');
|
||||
}
|
||||
}
|
321
extlib/XMPPHP/XMPP.php
Normal file
321
extlib/XMPPHP/XMPP.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/** XMPPHP_XMLStream */
|
||||
require_once "XMLStream.php";
|
||||
|
||||
/**
|
||||
* XMPPHP Main Class
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
* @version $Id$
|
||||
*/
|
||||
class XMPPHP_XMPP extends XMPPHP_XMLStream {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $resource;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fulljid;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $basejid;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $authed = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $auto_subscribe = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $use_encryption = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $host
|
||||
* @param integer $port
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $resource
|
||||
* @param string $server
|
||||
* @param boolean $printlog
|
||||
* @param string $loglevel
|
||||
*/
|
||||
public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
|
||||
parent::__construct($host, $port, $printlog, $loglevel);
|
||||
|
||||
$this->user = $user;
|
||||
$this->password = $password;
|
||||
$this->resource = $resource;
|
||||
if(!$server) $server = $host;
|
||||
$this->basejid = $this->user . '@' . $this->host;
|
||||
|
||||
$this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
|
||||
$this->stream_end = '</stream:stream>';
|
||||
$this->default_ns = 'jabber:client';
|
||||
|
||||
$this->addHandler('features', 'http://etherx.jabber.org/streams', 'features_handler');
|
||||
$this->addHandler('success', 'urn:ietf:params:xml:ns:xmpp-sasl', 'sasl_success_handler');
|
||||
$this->addHandler('failure', 'urn:ietf:params:xml:ns:xmpp-sasl', 'sasl_failure_handler');
|
||||
$this->addHandler('proceed', 'urn:ietf:params:xml:ns:xmpp-tls', 'tls_proceed_handler');
|
||||
$this->addHandler('message', 'jabber:client', 'message_handler');
|
||||
$this->addHandler('presence', 'jabber:client', 'presence_handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn encryption on/ff
|
||||
*
|
||||
* @param boolean $useEncryption
|
||||
*/
|
||||
public function useEncryption($useEncryption = true) {
|
||||
$this->use_encryption = $useEncryption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on auto-authorization of subscription requests.
|
||||
*
|
||||
* @param boolean $autoSubscribe
|
||||
*/
|
||||
public function autoSubscribe($autoSubscribe = true) {
|
||||
$this->auto_subscribe = $autoSubscribe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send XMPP Message
|
||||
*
|
||||
* @param string $to
|
||||
* @param string $body
|
||||
* @param string $type
|
||||
* @param string $subject
|
||||
*/
|
||||
public function message($to, $body, $type = 'chat', $subject = null, $payload = null) {
|
||||
$to = htmlspecialchars($to);
|
||||
$body = htmlspecialchars($body);
|
||||
$subject = htmlspecialchars($subject);
|
||||
|
||||
$out = "<message from='{$this->fulljid}' to='$to' type='$type'>";
|
||||
if($subject) $out .= "<subject>$subject</subject>";
|
||||
$out .= "<body>$body</body>";
|
||||
if($payload) $out .= $payload;
|
||||
$out .= "</message>";
|
||||
|
||||
$this->send($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Presence
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $show
|
||||
* @param string $to
|
||||
*/
|
||||
public function presence($status = null, $show = 'available', $to = null, $type='available') {
|
||||
if($type == 'available') $type = '';
|
||||
$to = htmlspecialchars($to);
|
||||
$status = htmlspecialchars($status);
|
||||
if($show == 'unavailable') $type = 'unavailable';
|
||||
|
||||
$out = "<presence";
|
||||
if($to) $out .= " to='$to'";
|
||||
if($type) $out .= " type='$type'";
|
||||
if($show == 'available' and !$status) {
|
||||
$out .= "/>";
|
||||
} else {
|
||||
$out .= ">";
|
||||
if($show != 'available') $out .= "<show>$show</show>";
|
||||
if($status) $out .= "<status>$status</status>";
|
||||
$out .= "</presence>";
|
||||
}
|
||||
|
||||
$this->send($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
public function message_handler($xml) {
|
||||
if(isset($xml->attrs['type'])) {
|
||||
$payload['type'] = $xml->attrs['type'];
|
||||
} else {
|
||||
$payload['type'] = 'chat';
|
||||
}
|
||||
$payload['from'] = $xml->attrs['from'];
|
||||
$payload['body'] = $xml->sub('body')->data;
|
||||
$this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG);
|
||||
$this->event('message', $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presence handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
public function presence_handler($xml) {
|
||||
$payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
|
||||
$payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
|
||||
$payload['from'] = $xml->attrs['from'];
|
||||
$payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
|
||||
$this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG);
|
||||
if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
|
||||
if($this->auto_subscribe) $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' /><presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
|
||||
$this->event('subscription_requested', $payload);
|
||||
} elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
|
||||
$this->event('subscription_accepted', $payload);
|
||||
} else {
|
||||
$this->event('presence', $payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Features handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function features_handler($xml) {
|
||||
if($xml->hasSub('starttls') and $this->use_encryption) {
|
||||
$this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
|
||||
} elseif($xml->hasSub('bind')) {
|
||||
$id = $this->getId();
|
||||
$this->addIdHandler($id, 'resource_bind_handler');
|
||||
$this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
|
||||
} else {
|
||||
$this->log->log("Attempting Auth...");
|
||||
$this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SASL success handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function sasl_success_handler($xml) {
|
||||
$this->log->log("Auth success!");
|
||||
$this->authed = true;
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* SASL feature handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function sasl_failure_handler($xml) {
|
||||
$this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR);
|
||||
$this->disconnect();
|
||||
|
||||
throw new XMPPHP_Exception('Auth failed!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource bind handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function resource_bind_handler($xml) {
|
||||
if($xml->attrs['type'] == 'result') {
|
||||
$this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
|
||||
$this->fulljid = $xml->sub('bind')->sub('jid')->data;
|
||||
}
|
||||
$id = $this->getId();
|
||||
$this->addIdHandler($id, 'session_start_handler');
|
||||
$this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the roster
|
||||
*
|
||||
*/
|
||||
public function getRoster() {
|
||||
$id = $this->getID();
|
||||
$this->addIdHandler($id, 'roster_get_handler');
|
||||
$this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Roster retrieval handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function roster_get_handler($xml) {
|
||||
// TODO: make this work
|
||||
}
|
||||
|
||||
/**
|
||||
* Session start handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function session_start_handler($xml) {
|
||||
$this->log->log("Session started");
|
||||
$this->event('session_start');
|
||||
}
|
||||
|
||||
/**
|
||||
* TLS proceed handler
|
||||
*
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function tls_proceed_handler($xml) {
|
||||
$this->log->log("Starting TLS encryption");
|
||||
stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
|
||||
$this->reset();
|
||||
}
|
||||
}
|
113
extlib/XMPPHP/XMPP_Old.php
Normal file
113
extlib/XMPPHP/XMPP_Old.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* XMPPHP: The PHP XMPP Library
|
||||
* Copyright (C) 2008 Nathanael C. Fritz
|
||||
* This file is part of SleekXMPP.
|
||||
*
|
||||
* XMPPHP is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* XMPPHP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with XMPPHP; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* @category xmpphp
|
||||
* @package XMPPHP
|
||||
* @author Nathanael C. Fritz <JID: fritzy@netflint.net>
|
||||
* @author Stephan Wentz <JID: stephan@jabber.wentz.it>
|
||||
* @copyright 2008 Nathanael C. Fritz
|
||||
*/
|
||||
|
||||
/** XMPPHP_XMPP
|
||||
*
|
||||
* This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's.
|
||||
* In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it.
|
||||
* The old Jabber protocol wasn't standardized, so use at your own risk.
|
||||
*
|
||||
*/
|
||||
require_once "XMPP.php";
|
||||
|
||||
class XMPPHP_XMPPOld extends XMPPHP_XMPP {
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $session_id;
|
||||
|
||||
public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
|
||||
parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel);
|
||||
if(!$server) $server = $host;
|
||||
$this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">';
|
||||
$this->fulljid = "{$user}@{$server}/{$resource}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Override XMLStream's startXML
|
||||
*
|
||||
* @param parser $parser
|
||||
* @param string $name
|
||||
* @param array $attr
|
||||
*/
|
||||
public function startXML($parser, $name, $attr) {
|
||||
if($this->xml_depth == 0) {
|
||||
$this->session_id = $attr['ID'];
|
||||
$this->authenticate();
|
||||
}
|
||||
parent::startXML($parser, $name, $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Authenticate Info Request
|
||||
*
|
||||
*/
|
||||
public function authenticate() {
|
||||
$id = $this->getId();
|
||||
$this->addidhandler($id, 'authfieldshandler');
|
||||
$this->send("<iq type='get' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username></query></iq>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve auth fields and send auth attempt
|
||||
*
|
||||
* @param XMLObj $xml
|
||||
*/
|
||||
public function authFieldsHandler($xml) {
|
||||
$id = $this->getId();
|
||||
$this->addidhandler($id, 'oldAuthResultHandler');
|
||||
if($xml->sub('query')->hasSub('digest')) {
|
||||
$hash = sha1($this->session_id . $this->password);
|
||||
print "{$this->session_id} {$this->password}\n";
|
||||
$out = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><digest>{$hash}</digest><resource>{$this->resource}</resource></query></iq>";
|
||||
} else {
|
||||
$out = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><password>{$this->password}</password><resource>{$this->resource}</resource></query></iq>";
|
||||
}
|
||||
$this->send($out);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine authenticated or failure
|
||||
*
|
||||
* @param XMLObj $xml
|
||||
*/
|
||||
public function oldAuthResultHandler($xml) {
|
||||
if($xml->attrs['type'] != 'result') {
|
||||
$this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR);
|
||||
$this->disconnect();
|
||||
throw new XMPPHP_Exception('Auth failed!');
|
||||
} else {
|
||||
$this->log->log("Session started");
|
||||
$this->event('session_start');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
1710
extlib/markdown.php
Normal file
1710
extlib/markdown.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,10 @@ define('NOTICES_PER_PAGE', 20);
|
||||
|
||||
define_syslog_variables();
|
||||
|
||||
# append our extlib dir as the last-resort place to find libs
|
||||
|
||||
set_include_path(array_merge(get_include_path(), array(INSTALLDIR . '/extlib/')));
|
||||
|
||||
# global configuration object
|
||||
|
||||
require_once('PEAR.php');
|
||||
@ -101,15 +105,16 @@ $config['db'] =
|
||||
'db_driver' => 'DB', # XXX: JanRain libs only work with DB
|
||||
'quote_identifiers' => false);
|
||||
|
||||
require_once(INSTALLDIR.'/config.php');
|
||||
|
||||
require_once('Validate.php');
|
||||
|
||||
if (function_exists('date_default_timezone_set')) {
|
||||
/* Work internally in UTC */
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
require_once(INSTALLDIR.'/config.php');
|
||||
|
||||
require_once('Validate.php');
|
||||
require_once('markdown.php');
|
||||
|
||||
require_once(INSTALLDIR.'/lib/util.php');
|
||||
require_once(INSTALLDIR.'/lib/action.php');
|
||||
require_once(INSTALLDIR.'/lib/theme.php');
|
||||
@ -122,5 +127,3 @@ function __autoload($class) {
|
||||
require_once(INSTALLDIR.'/classes/' . $class . '.php');
|
||||
}
|
||||
}
|
||||
|
||||
require_once('markdown.php');
|
||||
|
Loading…
Reference in New Issue
Block a user