Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a6d2793
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+This is the source code of Kolab Web Administration Panel
+
+
+INTALLATION PROCEDURE
+=====================
+
+This package uses [Composer][1] to install and maintain required PHP libraries.
+See doc/INSTALL for more details.
+
+1. Install Composer
+
+Execute this in the project root directory:
+
+$ curl -s http://getcomposer.org/installer | php
+
+This will create a file named composer.phar in the project directory.
+
+2. Install Dependencies
+
+$ cp composer.json-dist composer.json
+$ php composer.phar install
+
+4. Create database structure from doc/kolab_wap.sql.
+
+5. Configure/Add [kolab_wap] section of /etc/kolab/kolab.conf file.
+See doc/sample-kolab.conf.
+
+6. Give write access for the webserver user to the 'logs' and 'cache' folders:
+
+$ chown <www-user> logs
+$ chown <www-user> cache
+
+7. Configure your webserver to point to the public_html directory of this
+package as document root. See doc/kolab-webadmin.conf for an example Apache config.
+
+[1]: http://getcomposer.org
diff --git a/composer.json-dist b/composer.json-dist
new file mode 100644
index 0000000..03d0452
--- /dev/null
+++ b/composer.json-dist
@@ -0,0 +1,21 @@
+{
+ "name": "kolab/webadmin",
+ "description": "The Kolab Web Administration Panel",
+ "license": "AGPL-3.0+",
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://git.kolab.org/diffusion/PNL/php-net_ldap.git"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "pear/pear-core-minimal": "~1.10.1",
+ "pear/http_request2": "~2.3.0",
+ "pear/mail": "~1.4.1",
+ "pear/net_smtp": "~1.7.3",
+ "pear/net_ldap2": "~2.2.0",
+ "kolab/net_ldap3": "dev-master",
+ "smarty/smarty": "~3.1.31"
+ }
+}
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 06aed37..ec3f63d 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -1,1498 +1,1496 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
-require_once "Net/LDAP3.php";
-
/**
* Kolab LDAP handling abstraction class.
*/
class LDAP extends Net_LDAP3 {
private $conf;
/**
* Class constructor
*/
public function __construct($domain = null)
{
parent::__construct();
$this->conf = Conf::get_instance();
$this->config_set("debug", Log::mode() == Log::TRACE);
$this->config_set("log_hook", array($this, "_log"));
$this->config_set("config_root_dn", "cn=config");
$this->config_set("service_bind_dn", $this->conf->get("service_bind_dn"));
$this->config_set("service_bind_pw", $this->conf->get("service_bind_pw"));
$this->config_set("login_filter", $this->conf->get("kolab_wap", "login_filter"));
$this->config_set("vlv", $this->conf->get("ldap", "vlv", Conf::AUTO));
// configure the cache
$memcache_hosts = $this->conf->get('kolab_wap', 'memcache_hosts');
$this->config_set("memcache_pconnect", $this->conf->get('kolab_wap', 'memcache_pconnect', Conf::BOOL));
$this->config_set("memcache_hosts", $memcache_hosts);
$this->config_set("cache", !empty($memcache_hosts));
$this->config_set("domain_base_dn", $this->conf->get('ldap', 'domain_base_dn'));
$this->config_set("domain_filter", $this->conf->get('ldap', 'domain_filter'));
$this->config_set("domain_name_attribute", $this->conf->get('ldap', 'domain_name_attribute'));
// See if we are to connect to any domain explicitly defined.
if (empty($domain)) {
// If not, attempt to get the domain from the session.
if (isset($_SESSION['user'])) {
try {
$domain = $_SESSION['user']->get_domain();
} catch (Exception $e) {
$this->_log(LOG_WARNING, "LDAP: User not authenticated yet");
}
}
} else {
$this->_log(LOG_DEBUG, "LDAP: __construct() using domain $domain");
}
// Continue and default to the primary domain.
$this->domain = $domain ? $domain : $this->conf->get('primary_domain');
$unique_attr = $this->conf->get($domain, 'unique_attribute');
if (empty($unique_attr)) {
$unique_attr = $this->conf->get('ldap', 'unique_attribute');
}
if (empty($unique_attr)) {
$unique_attr = 'nsuniqueid';
}
$this->config_set('unique_attribute', $unique_attr);
$this->_ldap_uri = $this->conf->get('ldap_uri');
$this->_ldap_server = parse_url($this->_ldap_uri, PHP_URL_HOST);
$this->_ldap_port = parse_url($this->_ldap_uri, PHP_URL_PORT);
$this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME);
// Catch cases in which the ldap server port has not been explicitely defined
if (!$this->_ldap_port) {
if ($this->_ldap_scheme == "ldaps") {
$this->_ldap_port = 636;
}
else {
$this->_ldap_port = 389;
}
}
$this->config_set("host", $this->_ldap_server);
$this->config_set("port", $this->_ldap_port);
$this->config_set("use_tls", $this->_ldap_scheme == 'tls');
parent::connect();
// Attempt to get the root dn from the configuration file.
$root_dn = $this->conf->get($this->domain, "base_dn");
if (empty($root_dn)) {
// Fall back to a root dn from LDAP, or the standard root dn
$root_dn = $this->domain_root_dn($this->domain);
}
$this->config_set("root_dn", $root_dn);
}
/**********************************************************
*********** Public methods ************
**********************************************************/
/**
* Authentication
*
* @param string $username User name (DN or mail)
* @param string $password User password
*
* @return bool|string User ID or False on failure
*/
public function authenticate($username, $password, $domain = null, &$attributes = null)
{
$this->_log(LOG_DEBUG, "Auth::LDAP: authentication request for $username against domain $domain");
if (!$domain) {
$domain = $this->domain;
}
$result = $this->login($username, $password, $domain, $attributes);
if (!$result) {
return false;
}
$_SESSION['user']->user_bind_dn = $result;
$_SESSION['user']->user_bind_pw = $password;
return $result;
}
public function domain_add($domain, $attributes = array())
{
if (empty($domain)) {
return false;
}
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
$domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
$service_bind_dn = $this->conf->get('ldap', 'service_bind_dn');
$primary_domain = $this->conf->get('kolab', 'primary_domain');
if (!empty($attributes['domainrelatedobject_only'])) {
$domainrelatedobject_only = (bool)($attributes['domainrelatedobject_only']);
unset($attributes['domainrelatedobject_only']);
} else {
$domainrelatedobject_only = false;
}
if (empty($service_bind_dn)) {
$service_bind_dn = $this->conf->get('ldap', 'bind_dn');
}
if (empty($domain_name_attribute)) {
$domain_name_attribute = 'associateddomain';
}
if (!is_array($attributes[$domain_name_attribute])) {
$attributes[$domain_name_attribute] = (array) $attributes[$domain_name_attribute];
}
if (!in_array($domain, $attributes[$domain_name_attribute])) {
array_unshift($attributes[$domain_name_attribute], $domain);
}
$domain_dn = $domain_name_attribute . '=' . $domain . ',' . $domain_base_dn;
if (!empty($attributes['inetdomainbasedn'])) {
$inetdomainbasedn = $attributes['inetdomainbasedn'];
}
else {
$inetdomainbasedn = $this->_standard_root_dn($domain);
}
if (empty($attributes['aci'])) {
$attributes['aci'] = array(
"(targetattr = \"*\") (version 3.0;acl \"Read Access for {$domain} Users\";allow (read,compare,search)(userdn = \"ldap:///{$inetdomainbasedn}??sub?(objectclass=*)\");)"
);
}
$result = $this->add_entry($domain_dn, $attributes);
if (!$result) {
return false;
}
// Return if the request specified to only create the domainrelatedobject
if ($domainrelatedobject_only) {
return true;
}
// Query the ACI for the primary domain
if ($domain_entry = $this->find_domain($primary_domain)) {
if (in_array('inetdomainbasedn', $domain_entry)) {
$_base_dn = $domain_entry['inetdomainbasedn'];
}
}
if (empty($_base_dn)) {
$_base_dn = $this->_standard_root_dn($primary_domain);
}
$result = $this->_read($_base_dn, array('aci'));
$result = $result[key($result)];
$acis = $result['aci'];
// Skip one particular ACI
foreach ($acis as $aci) {
if (stristr($aci, "SIE Group") === false) {
continue;
}
$_aci = $aci;
}
// @TODO: this list should be configurable or auto-created somehow
$self_attrs = array(
'carLicense', 'description', 'displayName', 'facsimileTelephoneNumber', 'homePhone',
'homePostalAddress', 'initials', 'jpegPhoto', 'l', 'labeledURI', 'mobile', 'o', 'pager', 'photo',
'postOfficeBox', 'postalAddress', 'postalCode', 'preferredDeliveryMethod', 'preferredLanguage',
'registeredAddress', 'roomNumber', 'secretary', 'seeAlso', 'st', 'street', 'telephoneNumber',
'telexNumber', 'title', 'userCertificate', 'userPassword', 'userSMIMECertificate',
'x500UniqueIdentifier',
);
if (in_array('kolabInetOrgPerson', $this->classes_allowed())) {
$self_attrs = array_merge($self_attrs, array('kolabDelegate', 'kolabInvitationPolicy', 'kolabAllowSMTPSender'));
}
$_domain = str_replace('.', '_', $domain);
$dn = $inetdomainbasedn;
$cn = str_replace(array(',', '='), array('\2C', '\3D'), $dn);
// Additional domain entries in various trees
$entries = array(
"cn={$cn},cn=mapping tree,cn=config" => array(
'objectclass' => array(
'top',
'extensibleObject',
'nsMappingTree',
),
'nsslapd-state' => 'backend',
'cn' => $inetdomainbasedn,
'nsslapd-backend' => $_domain,
),
"cn={$_domain},cn=ldbm database,cn=plugins,cn=config" => array(
'objectclass' => array(
'top',
'extensibleobject',
'nsbackendinstance',
),
'cn' => $_domain,
'nsslapd-suffix' => $inetdomainbasedn,
'nsslapd-cachesize' => '-1',
'nsslapd-cachememsize' => '10485760',
'nsslapd-readonly' => 'off',
'nsslapd-require-index' => 'off',
'nsslapd-dncachememsize' => '10485760',
'nsslapd-directory' => true, // will be replaced below
),
$inetdomainbasedn => array(
// @TODO: Probably just use ldap_explode_dn()
'dc' => substr($dn, (strpos($dn, '=')+1), ((strpos($dn, ',')-strpos($dn, '='))-1)),
'objectclass' => array(
'top',
'domain',
),
'aci' => array(
// Self-modification
"(targetattr = \"" . implode(" || ", $self_attrs) . "\")(version 3.0; acl \"Enable self write for common attributes\"; allow (read,compare,search,write) userdn=\"ldap:///self\";)",
// Directory Administrators
"(targetattr = \"*\")(version 3.0; acl \"Directory Administrators Group\"; allow (all) (groupdn=\"ldap:///cn=Directory Administrators,{$inetdomainbasedn}\" or roledn=\"ldap:///cn=kolab-admin,{$inetdomainbasedn}\");)",
// Configuration Administrators
"(targetattr = \"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)",
// Administrator users
"(targetattr = \"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)",
// SIE Group
$_aci,
// Search Access,
"(targetattr != \"userPassword\") (version 3.0; acl \"Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$inetdomainbasedn}??sub?(objectclass=*)\");)",
// Service Search Access
"(targetattr = \"*\") (version 3.0; acl \"Service Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$service_bind_dn}\");)",
),
),
);
$replica_hosts = $this->list_replicas();
if (!empty($replica_hosts)) {
foreach ($replica_hosts as $replica_host) {
$ldap = new Net_LDAP3($this->config);
$ldap->config_set("log_hook", array($this, "_log"));
$ldap->config_set('hosts', array($replica_host));
$ldap->connect();
$ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
foreach ($entries as $dn => $attrs) {
if (isset($attrs['nsslapd-directory'])) {
$attrs['nsslapd-directory'] = $this->nsslapd_directory($ldap, $domain);
}
if (!$ldap->add_entry($dn, $attrs)) {
$this->_log(LOG_ERR, "Error adding $dn to $replica_host");
}
}
$ldap->close();
}
}
else {
foreach ($entries as $dn => $attrs) {
if (isset($attrs['nsslapd-directory'])) {
$attrs['nsslapd-directory'] = $this->nsslapd_directory($this, $domain);
}
if (!$this->add_entry($dn, $attrs)) {
$this->_log(LOG_ERR, "Error adding $dn");
}
}
}
if (!empty($replica_hosts)) {
$this->add_replication_agreements($inetdomainbasedn);
}
// add OUs, do this after adding replication agreements
$entries = array(
"cn=Directory Administrators,$inetdomainbasedn" => array(
'cn' => 'Directory Administrators',
'objectclass' => array('top', 'groupofuniquenames'),
'uniquemember' => array('cn=Directory Manager'),
),
"cn=kolab-admin,$inetdomainbasedn" => array(
'cn' => 'kolab-admin',
'objectclass' => array(
'top',
'ldapsubentry',
'nsroledefinition',
'nssimpleroledefinition',
'nsmanagedroledefinition',
),
),
// @TODO: these OUs DN should be read from config
"ou=Groups,$inetdomainbasedn" => array(
'ou' => 'Groups',
'objectclass' => array('top', 'organizationalunit'),
),
"ou=People,$inetdomainbasedn" => array(
'ou' => 'People',
'objectclass' => array('top', 'organizationalunit'),
),
"ou=Special Users,$inetdomainbasedn" => array(
'ou' => 'Special Users',
'objectclass' => array('top', 'organizationalunit'),
),
"ou=Resources,$inetdomainbasedn" => array(
'ou' => 'Resources',
'objectclass' => array('top', 'organizationalunit'),
),
"ou=Shared Folders,$inetdomainbasedn" => array(
'ou' => 'Shared Folders',
'objectclass' => array('top', 'organizationalunit'),
),
);
// create set of OUs and other domain entries
foreach ($entries as $dn => $attrs) {
$this->add_entry($dn, $attrs);
}
return $domain_dn;
}
public function domain_edit($domain, $attributes, $typeid = null)
{
$domain = $this->domain_info($domain, array_keys($attributes));
if (empty($domain)) {
return false;
}
$domain_dn = key($domain);
// We should start throwing stuff over the fence here.
return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
}
public function domain_delete($domain)
{
$domain = $this->domain_info($domain);
if (empty($domain)) {
return false;
}
$domain_dn = key($domain);
$attributes = array_merge($domain[$domain_dn], array('inetdomainstatus' => 'deleted'));
// for performance reasons we set only domain status,
// cronjob script should delete such domain later
return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
}
public function domain_find_by_attribute($attribute)
{
$base_dn = $this->conf->get('ldap', 'domain_base_dn');
return $this->entry_find_by_attribute($attribute, $base_dn);
}
public function domain_info($domain, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() for domain " . var_export($domain, true));
if (empty($domain)) {
return false;
}
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
$domain_dn = $this->entry_dn($domain, array(), $domain_base_dn);
if (!$domain_dn) {
if ($result = $this->find_domain($domain, $attributes)) {
$result_dn = $result['dn'];
unset($result['dn']);
$result = array($result_dn => $result);
}
}
else {
$result = $this->_read($domain_dn, $attributes);
}
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true));
return $result ? $result : false;
}
/**
* Checkes if specified domain is empty (no users assigned)
*
* @param string|array $domain Domain name or domain_info() result
*
* @return bool True if domain is empty, False otherwise
*/
public function domain_is_empty($domain)
{
$domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
if (empty($domain_name_attribute)) {
$domain_name_attribute = 'associateddomain';
}
if (!is_array($domain)) {
$domain = $this->domain_info($domain);
}
if (!empty($domain)) {
$domain_dn = key($domain);
$domain_name = $domain[$domain_dn][$domain_name_attribute];
if (is_array($domain_name)) {
$domain_name = $domain_name[0];
}
}
else {
return false;
}
$result = $this->list_users(array('entrydn'), null, array('page_size' => 1), $domain_name);
return is_array($result) && $result['count'] == 0;
}
/**
* Proxy to parent function in order to enable us to insert our
* configuration.
*/
public function effective_rights($subject)
{
$ckey = $_SESSION['user']->user_bind_dn . '#'
. md5($this->domain . '::' . $subject . '::' . $_SESSION['user']->user_bind_pw);
// use memcache
if ($result = $this->get_cache_data($ckey)) {
return $result;
}
// use internal cache
else if (isset($this->icache[$ckey])) {
return $this->icache[$ckey];
}
// Ensure we are bound with the user's credentials
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$this->_log(LOG_DEBUG, "Auth::LDAP::effective_rights(\$subject = '" . $subject . "')");
switch ($subject) {
case "domain":
$result = parent::effective_rights($this->conf->get("ldap", "domain_base_dn"));
break;
case "group":
case "ou":
case "resource":
case "role":
case "sharedfolder":
case "user":
$result = parent::effective_rights($this->_subject_base_dn($subject));
break;
default:
$result = parent::effective_rights($subject);
}
if (!$result) {
$result = $this->legacy_rights($subject);
}
if (!$this->set_cache_data($ckey, $result)) {
$this->icache[$ckey] = $result;
}
return $result;
}
public function find_recipient($address)
{
if (strpos($address, '@') === false) {
return false;
}
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$root_dn = $this->config_get('root_dn');
$mail_attrs = $this->conf->get_list('mail_attributes') ?: array('mail', 'alias');
$search = array('operator' => 'OR');
foreach ($mail_attrs as $num => $attr) {
$search['params'][$attr] = array(
'type' => 'exact',
'value' => $address,
);
}
$result = $this->search_entries($root_dn, '(objectclass=*)', 'sub', $mail_attrs,
array('search' => $search));
if ($result && $result->count() > 0) {
return $result->entries(true);
}
return false;
}
public function get_attributes($subject_dn, $attributes)
{
$this->_log(LOG_DEBUG, "Auth::LDAP::get_attributes() for $subject_dn");
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
return $this->get_entry_attributes($subject_dn, $attributes);
}
public function group_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('group', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function group_delete($group)
{
return $this->entry_delete($group);
}
public function group_edit($group, $attributes, $typeid = null)
{
$group = $this->group_info($group, array_keys($attributes));
if (empty($group)) {
return false;
}
$group_dn = key($group);
// We should start throwing stuff over the fence here.
return $this->modify_entry($group_dn, $group[$group_dn], $attributes);
}
public function group_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function group_info($group, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$group_dn = $this->entry_dn($group);
if (!$group_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($group_dn, $attributes);
}
public function group_members_list($group, $recurse = true)
{
$group_dn = $this->entry_dn($group);
if (!$group_dn) {
return false;
}
return $this->list_group_members($group_dn, null, $recurse);
}
public function list_domains($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_domains(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$section = $this->conf->get('kolab', 'auth_mechanism');
$base_dn = $this->conf->get($section, 'domain_base_dn');
$filter = $this->conf->get($section, 'domain_filter');
$kolab_filter = $this->conf->get($section, 'kolab_domain_filter');
if (empty($filter) && !empty($kolab_filter)) {
$filter = $kolab_filter;
}
if (!$filter) {
$filter = "(associateddomain=*)";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_groups($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_groups(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('group');
$filter = $this->conf->get('group_filter');
if (!$filter) {
$filter = "(|(objectclass=groupofuniquenames)(objectclass=groupofurls))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_organizationalunits($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_organizationalunits(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn($params['type'] ? $params['type'] . '_ou' : 'ou');
$filter = $this->conf->get('ou_filter');
if (!$filter) {
$filter = "(objectclass=organizationalunit)";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_resources($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_resources(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('resource');
$filter = $this->conf->get('resource_filter');
if (!$filter) {
$filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_roles($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_roles(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('role');
$filter = $this->conf->get('role_filter');
if (empty($filter)) {
$filter = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_sharedfolders($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_sharedfolders(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('sharedfolder');
$filter = $this->conf->get('sharedfolder_filter');
if (!$filter) {
$filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_users($attributes = array(), $search = array(), $params = array(), $domain = null)
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true) . ", " . $domain . ")");
$base_dn = $this->_subject_base_dn('user', false, $domain);
$filter = $this->conf->get('user_filter');
if (empty($filter)) {
$filter = "(objectclass=kolabinetorgperson)";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function organizationalunit_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('ou', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]ou_rdn_attr
$dn = "ou=" . Net_LDAP3::quote_string($attrs['ou'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function organizationalunit_edit($ou, $attributes, $typeid = null)
{
$ou = $this->organizationalunit_info($ou, array_keys($attributes));
if (empty($ou)) {
return false;
}
$dn = key($ou);
// We should start throwing stuff over the fence here.
return $this->modify_entry($dn, $ou[$dn], $attributes);
}
public function organizationalunit_delete($ou)
{
return $this->entry_delete($ou, array('objectclass' => 'organizationalunit'));
}
public function organizationalunit_find_by_attribute($attribute)
{
$attribute['objectclass'] = 'organizationalunit';
return $this->entry_find_by_attribute($attribute);
}
public function organizationalunit_info($ou, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::organizationalunit_info() for unit " . var_export($ou, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$dn = $this->entry_dn($ou, array('objectclass' => 'organizationalunit'));
if (!$dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($dn, $attributes);
}
public function resource_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('resource', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]resource_rdn_attr
$dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function resource_delete($resource)
{
return $this->entry_delete($resource);
}
public function resource_edit($resource, $attributes, $typeid = null)
{
$resource = $this->resource_info($resource, array_keys($attributes));
if (empty($resource)) {
return false;
}
$resource_dn = key($resource);
// We should start throwing stuff over the fence here.
return $this->modify_entry($resource_dn, $resource[$resource_dn], $attributes);
}
public function resource_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function resource_info($resource, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::resource_info() for resource " . var_export($resource, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$resource_dn = $this->entry_dn($resource);
if (!$resource_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($resource_dn, $attributes);
}
public function role_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('role', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]role_rdn_attr
$dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function role_edit($role, $attributes, $typeid = null)
{
$role = $this->role_info($role, array_keys($attributes));
if (empty($role)) {
return false;
}
$role_dn = key($role);
// We should start throwing stuff over the fence here.
return $this->modify_entry($role_dn, $role[$role_dn], $attributes);
}
public function role_delete($role)
{
return $this->entry_delete($role, array('objectclass' => 'ldapsubentry'));
}
public function role_find_by_attribute($attribute)
{
$attribute['objectclass'] = 'ldapsubentry';
return $this->entry_find_by_attribute($attribute);
}
public function role_info($role, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::role_info() for role " . var_export($role, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$role_dn = $this->entry_dn($role, array('objectclass' => 'ldapsubentry'));
if (!$role_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($role_dn, $attributes);
}
public function sharedfolder_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('sharedfolder', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function sharedfolder_delete($sharedfolder)
{
return $this->entry_delete($sharedfolder);
}
public function sharedfolder_edit($sharedfolder, $attributes, $typeid = null)
{
$sharedfolder = $this->sharedfolder_info($sharedfolder, array_keys($attributes));
if (empty($sharedfolder)) {
return false;
}
$sharedfolder_dn = key($sharedfolder);
// We should start throwing stuff over the fence here.
return $this->modify_entry($sharedfolder_dn, $sharedfolder[$sharedfolder_dn], $attributes);
}
public function sharedfolder_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function sharedfolder_info($sharedfolder, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::sharedfolder_info() for sharedfolder " . var_export($sharedfolder, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$sharedfolder_dn = $this->entry_dn($sharedfolder);
if (!$sharedfolder_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($sharedfolder_dn, $attributes);
}
public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attributes = null, $props = array(), $count_only = false)
{
if (isset($_SESSION['user']->user_bind_dn) && !empty($_SESSION['user']->user_bind_dn)) {
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
}
return parent::search($base_dn, $filter, $scope, $attributes ?: array('*'), $props, $count_only);
}
public function subject_base_dn($subject, $strict = false)
{
return $this->_subject_base_dn($subject, $strict);
}
public function user_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('user', $typeid, $attrs);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "uid=" . Net_LDAP3::quote_string($attrs['uid'], true) . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function user_edit($user, $attributes, $typeid = null)
{
$user = $this->user_info($user, array_keys($attributes));
if (empty($user)) {
return false;
}
$user_dn = key($user);
// We should start throwing stuff over the fence here.
$result = $this->modify_entry($user_dn, $user[$user_dn], $attributes);
// Handle modification of current user data
if (!empty($result) && $user_dn == $_SESSION['user']->user_bind_dn) {
// update session password
if (!empty($result['replace']) && !empty($result['replace']['userpassword'])) {
$pass = $result['replace']['userpassword'];
$_SESSION['user']->user_bind_pw = is_array($pass) ? implode($pass) : $pass;
}
}
return $result;
}
public function user_delete($user)
{
return $this->entry_delete($user);
}
public function user_info($user, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$user_dn = $this->entry_dn($user);
if (!$user_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($user_dn, $attributes);
}
public function user_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
/**
* Returns attributes available in specified object classes
*/
public function attributes_allowed($objectclasses = array())
{
$attributes = parent::attributes_allowed($objectclasses);
// additional special attributes that aren't in LDAP schema
$additional_attributes = array(
'top' => array('nsRoleDN'),
'*' => array('aci'),
);
if (!empty($attributes)) {
foreach ($additional_attributes as $class => $attrs) {
if (in_array($class, $objectclasses)) {
$attributes['may'] = array_merge($attributes['may'], $attrs);
}
}
$attributes['may'] = array_merge($attributes['may'], $additional_attributes['*']);
}
return $attributes;
}
/**
* Find domain by name
*
* @param string $domain Domain name
* @param array $attributes Result attributes
*
* @return array|bool Domain attributes (+ 'dn' attribute) or False on error
*/
public function find_domain($domain, $attributes = array('*'))
{
if (empty($domain)) {
return false;
}
$ckey = 'domain::' . $domain . '::' . md5(implode(',', $attributes));
if (isset($this->icache[$ckey])) {
return $this->icache[$ckey];
}
// connect and bind...
if ($_SESSION['user'] && $_SESSION['user']->user_bind_dn) {
$bind_dn = $_SESSION['user']->user_bind_dn;
$bind_pw = $_SESSION['user']->user_bind_pw;
}
else {
$bind_dn = $this->conf->get('service_bind_dn');
$bind_pw = $this->conf->get('service_bind_pw');
}
if (!$this->bind($bind_dn, $bind_pw)) {
return false;
}
return parent::find_domain($domain, $attributes);
}
/**
* Wrapper for search_entries()
*/
protected function _list($base_dn, $filter, $scope, $attributes, $search, $params)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
if (!empty($params['sort_by'])) {
if (is_array($params['sort_by'])) {
foreach ($params['sort_by'] as $attrib) {
if (!in_array($attrib, $attributes)) {
$attributes[] = $attrib;
}
}
} else {
if (!in_array($params['sort_by'], $attributes)) {
$attributes[] = $params['sort_by'];
}
}
}
if (!empty($params['page_size'])) {
$this->config_set('page_size', $params['page_size']);
} else {
$this->config_set('page_size', 15);
}
if (!empty($params['page'])) {
$this->config_set('list_page', $params['page']);
} else {
$this->config_set('list_page', 1);
}
if (empty($attributes) || !is_array($attributes)) {
$attributes = array('*');
}
// LDAP3 search parameters
$opts = array(
'search' => $search,
'sort' => $params['sort_by'], // for VLV
);
$result = $this->search_entries($base_dn, $filter, $scope, $attributes, $opts);
$entries = $this->sort_and_slice($result, $params);
return array(
'list' => $entries,
'count' => is_object($result) ? $result->count() : 0,
);
}
/**
* Prepare environment before _read() call
*/
protected function read_prepare(&$attributes)
{
// always return unique attribute
$unique_attr = $this->conf->get('unique_attribute');
if (empty($unique_attr)) {
$unique_attr = 'nsuniqueid';
}
$this->_log(LOG_NOTICE, "Using unique_attribute " . var_export($unique_attr, TRUE) . " at " . __FILE__ . ":" . __LINE__);
if (!in_array($unique_attr, $attributes)) {
$attributes[] = $unique_attr;
}
}
/**
* delete_entry() wrapper with binding and DN resolving
*/
protected function entry_delete($entry, $attributes = array(), $base_dn = null)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$entry_dn = $this->entry_dn($entry, $attributes, $base_dn);
// object not found or self deletion
if (!$entry_dn) {
return false;
}
return $this->delete_entry($entry_dn);
}
/**
* add_entry() wrapper with binding
*/
protected function entry_add($entry_dn, $attrs)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
if ($this->add_entry($entry_dn, $attrs)) {
return $entry_dn;
}
return false;
}
/**
* Return base DN for specified object type
*/
protected function entry_base_dn($type, $typeid = null, &$attrs = array())
{
// check if base_dn already exists in object attributes
if (!empty($attrs)) {
if (!empty($attrs['base_dn'])) {
$base_dn = $attrs['base_dn'];
unset($attrs['base_dn']);
}
else if ($type != 'ou' && !empty($attrs['ou'])) {
$base_dn = $attrs['ou'];
unset($attrs['ou']);
}
}
if (empty($base_dn) && $typeid) {
$db = SQL::get_instance();
$query = $db->query("SELECT `key` FROM `{$type}_types` WHERE `id` = ?", array($typeid));
$sql = $db->fetch_assoc($query);
// Check if the type has a specific base DN specified.
$base_dn = $this->_subject_base_dn($sql['key'] . '_' . $type, true);
}
if (empty($base_dn)) {
$base_dn = $this->_subject_base_dn($type);
}
return $base_dn;
}
public function _log($level, $msg)
{
if (strstr($_SERVER["REQUEST_URI"], "/api/")) {
$str = "(api) ";
} else {
$str = "";
}
if (is_array($msg)) {
$msg = implode("\n", $msg);
}
switch ($level) {
case LOG_DEBUG:
Log::debug($str . $msg);
break;
case LOG_ERR:
case LOG_ALERT:
case LOG_CRIT:
case LOG_EMERG:
Log::error($str . $msg);
break;
case LOG_INFO:
Log::info($str . $msg);
break;
case LOG_WARNING:
Log::warning($str . $msg);
break;
case LOG_NOTICE:
default:
Log::trace($str . $msg);
break;
}
}
private function _subject_base_dn($subject, $strict = false, $domain = null)
{
if (empty($domain)) {
$domain = $this->domain;
}
$subject_base_dn = $this->conf->get_raw($domain, $subject . "_base_dn");
if (empty($subject_base_dn)) {
$subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn");
}
// This could be "<object_type>_<object_name>", if so we'll try the name only now
if (empty($subject_base_dn) && ($pos = strrpos($subject, '_'))) {
$subject = substr($subject, $pos + 1);
$subject_base_dn = $this->conf->get_raw($domain, $subject . "_base_dn");
if (empty($subject_base_dn)) {
$subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn");
}
}
if (empty($subject_base_dn) && $strict) {
$this->_log(LOG_DEBUG, "subject_base_dn for subject $subject not found");
return null;
}
// Attempt to get a configured base_dn
$base_dn = $this->conf->get($domain, "base_dn");
if (empty($base_dn)) {
$base_dn = $this->domain_root_dn($domain);
}
if (!empty($subject_base_dn)) {
$base_dn = $this->conf->expand($subject_base_dn, array("base_dn" => $base_dn));
}
$this->_log(LOG_DEBUG, "subject_base_dn for subject $subject is $base_dn");
return $base_dn;
}
private function legacy_rights($subject)
{
$subject_dn = $this->entry_dn($subject);
$user_is_admin = false;
$user_is_self = false;
// List group memberships
$user_groups = $this->find_user_groups($_SESSION['user']->user_bind_dn);
foreach ($user_groups as $user_group_dn) {
if ($user_is_admin)
continue;
$user_group_dn_components = ldap_explode_dn($user_group_dn, 1);
unset($user_group_dn_components["count"]);
$user_group_cn = array_shift($user_group_dn_components);
if (in_array($user_group_cn, array('admin', 'maintainer', 'domain-maintainer'))) {
// All rights default to write.
$user_is_admin = true;
} else {
// The user is a regular user, see if the subject is the same has the
// user session's bind_dn.
if ($subject_dn == $_SESSION['user']->user_bind_dn) {
$user_is_self = true;
}
}
}
if ($user_is_admin) {
$standard_rights = array("add", "delete", "read", "write");
} elseif ($user_is_self) {
$standard_rights = array("read", "write");
} else {
$standard_rights = array("read");
}
$rights = array(
'entryLevelRights' => $standard_rights,
'attributeLevelRights' => array(),
);
$subject = $this->search($subject_dn);
if (!$subject) {
return $rights;
}
$subject = $subject->entries(true);
$attributes = $this->attributes_allowed($subject[$subject_dn]['objectclass']);
$attributes = array_merge((array)$attributes['may'], (array)$attributes['must']);
foreach ($attributes as $attribute) {
$rights['attributeLevelRights'][$attribute] = $standard_rights;
}
return $rights;
}
private function sort_and_slice(&$result, &$params)
{
if (!is_object($result)) {
return array();
}
$entries = $result->entries(true);
if ($this->vlv_active) {
return $entries;
}
if (!empty($params) && is_array($params)) {
if (!empty($params['sort_by'])) {
$this->sort_result_key = $params['sort_by'];
uasort($entries, array($this, 'sort_result'));
}
if (!empty($params['sort_order']) && $params['sort_order'] == "DESC") {
$entries = array_reverse($entries, true);
}
if (!empty($params['page_size']) && !empty($params['page'])) {
if ($result->count() > $params['page_size']) {
$entries = array_slice($entries, (($params['page'] - 1) * $params['page_size']), $params['page_size'], true);
}
}
}
return $entries;
}
/**
* Result sorting callback for uasort()
*/
private function sort_result($a, $b)
{
if (is_array($this->sort_result_key)) {
foreach ($this->sort_result_key as $attrib) {
if (array_key_exists($attrib, $a) && !$str1) {
$str1 = $a[$attrib];
}
if (array_key_exists($attrib, $b) && !$str2) {
$str2 = $b[$attrib];
}
}
} else {
$str1 = $a[$this->sort_result_key];
$str2 = $b[$this->sort_result_key];
}
if (is_array($str1)) {
$str1 = array_shift($str1);
}
if (is_array($str2)) {
$str2 = array_shift($str2);
}
return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
}
/**
* Qualify a username.
*
* Where username is 'kanarip@kanarip.com', the function will return an
* array containing 'kanarip' and 'kanarip.com'. However, where the
* username is 'kanarip', the domain name is to be assumed the
* management domain name.
*/
private function _qualify_id($username)
{
$username_parts = explode('@', $username);
if (count($username_parts) == 1) {
$domain_name = $this->conf->get('primary_domain');
}
else {
$domain_name = array_pop($username_parts);
}
return array(implode('@', $username_parts), $domain_name);
}
/***********************************************************
************ Shortcut functions ****************
***********************************************************/
protected function _read($entry_dn, $attributes = array('*'))
{
$result = $this->search($entry_dn, '(objectclass=*)', 'base', $attributes);
if ($result) {
$this->_log(LOG_DEBUG, "Auth::LDAP::_read() result: " . var_export($result->entries(true), true));
return $result->entries(true);
}
else {
return false;
}
}
/**
* Finds nsslapd-directory for specified domain
*/
protected function nsslapd_directory($ldap, $domain)
{
$primary_domain = $this->conf->get('kolab', 'primary_domain');
$_primary_domain = str_replace('.', '_', $primary_domain);
$_domain = str_replace('.', '_', $domain);
$roots = array($_primary_domain, $primary_domain, 'userRoot');
foreach ($roots as $root) {
if ($result = $ldap->get_entry("cn=$root,cn=ldbm database,cn=plugins,cn=config")) {
break;
}
}
$this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
$result = $result[key($result)];
$orig_directory = $result['nsslapd-directory'];
$directory = $orig_directory;
reset($roots);
foreach ($roots as $root) {
if ($directory == $orig_directory) {
$directory = str_replace($root, $_domain, $result['nsslapd-directory']);
}
}
$this->_log(LOG_DEBUG, "nsslapd-directory for domain $domain is $directory");
return $directory;
}
}
diff --git a/lib/ext/HTTP/Request2.php b/lib/ext/HTTP/Request2.php
deleted file mode 100644
index 6d8d953..0000000
--- a/lib/ext/HTTP/Request2.php
+++ /dev/null
@@ -1,1015 +0,0 @@
-<?php
-/**
- * Class representing a HTTP request message
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Request2.php 315409 2011-08-24 07:29:23Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * A class representing an URL as per RFC 3986.
- */
-require_once 'Net/URL2.php';
-
-/**
- * Exception class for HTTP_Request2 package
- */
-require_once 'HTTP/Request2/Exception.php';
-
-/**
- * Class representing a HTTP request message
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- * @link http://tools.ietf.org/html/rfc2616#section-5
- */
-class HTTP_Request2 implements SplSubject
-{
- /**#@+
- * Constants for HTTP request methods
- *
- * @link http://tools.ietf.org/html/rfc2616#section-5.1.1
- */
- const METHOD_OPTIONS = 'OPTIONS';
- const METHOD_GET = 'GET';
- const METHOD_HEAD = 'HEAD';
- const METHOD_POST = 'POST';
- const METHOD_PUT = 'PUT';
- const METHOD_DELETE = 'DELETE';
- const METHOD_TRACE = 'TRACE';
- const METHOD_CONNECT = 'CONNECT';
- /**#@-*/
-
- /**#@+
- * Constants for HTTP authentication schemes
- *
- * @link http://tools.ietf.org/html/rfc2617
- */
- const AUTH_BASIC = 'basic';
- const AUTH_DIGEST = 'digest';
- /**#@-*/
-
- /**
- * Regular expression used to check for invalid symbols in RFC 2616 tokens
- * @link http://pear.php.net/bugs/bug.php?id=15630
- */
- const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';
-
- /**
- * Regular expression used to check for invalid symbols in cookie strings
- * @link http://pear.php.net/bugs/bug.php?id=15630
- * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html
- */
- const REGEXP_INVALID_COOKIE = '/[\s,;]/';
-
- /**
- * Fileinfo magic database resource
- * @var resource
- * @see detectMimeType()
- */
- private static $_fileinfoDb;
-
- /**
- * Observers attached to the request (instances of SplObserver)
- * @var array
- */
- protected $observers = array();
-
- /**
- * Request URL
- * @var Net_URL2
- */
- protected $url;
-
- /**
- * Request method
- * @var string
- */
- protected $method = self::METHOD_GET;
-
- /**
- * Authentication data
- * @var array
- * @see getAuth()
- */
- protected $auth;
-
- /**
- * Request headers
- * @var array
- */
- protected $headers = array();
-
- /**
- * Configuration parameters
- * @var array
- * @see setConfig()
- */
- protected $config = array(
- 'adapter' => 'HTTP_Request2_Adapter_Socket',
- 'connect_timeout' => 10,
- 'timeout' => 0,
- 'use_brackets' => true,
- 'protocol_version' => '1.1',
- 'buffer_size' => 16384,
- 'store_body' => true,
-
- 'proxy_host' => '',
- 'proxy_port' => '',
- 'proxy_user' => '',
- 'proxy_password' => '',
- 'proxy_auth_scheme' => self::AUTH_BASIC,
-
- 'ssl_verify_peer' => true,
- 'ssl_verify_host' => true,
- 'ssl_cafile' => null,
- 'ssl_capath' => null,
- 'ssl_local_cert' => null,
- 'ssl_passphrase' => null,
-
- 'digest_compat_ie' => false,
-
- 'follow_redirects' => false,
- 'max_redirects' => 5,
- 'strict_redirects' => false
- );
-
- /**
- * Last event in request / response handling, intended for observers
- * @var array
- * @see getLastEvent()
- */
- protected $lastEvent = array(
- 'name' => 'start',
- 'data' => null
- );
-
- /**
- * Request body
- * @var string|resource
- * @see setBody()
- */
- protected $body = '';
-
- /**
- * Array of POST parameters
- * @var array
- */
- protected $postParams = array();
-
- /**
- * Array of file uploads (for multipart/form-data POST requests)
- * @var array
- */
- protected $uploads = array();
-
- /**
- * Adapter used to perform actual HTTP request
- * @var HTTP_Request2_Adapter
- */
- protected $adapter;
-
- /**
- * Cookie jar to persist cookies between requests
- * @var HTTP_Request2_CookieJar
- */
- protected $cookieJar = null;
-
- /**
- * Constructor. Can set request URL, method and configuration array.
- *
- * Also sets a default value for User-Agent header.
- *
- * @param string|Net_Url2 Request URL
- * @param string Request method
- * @param array Configuration for this Request instance
- */
- public function __construct($url = null, $method = self::METHOD_GET, array $config = array())
- {
- $this->setConfig($config);
- if (!empty($url)) {
- $this->setUrl($url);
- }
- if (!empty($method)) {
- $this->setMethod($method);
- }
- $this->setHeader('user-agent', 'HTTP_Request2/2.0.0 ' .
- '(http://pear.php.net/package/http_request2) ' .
- 'PHP/' . phpversion());
- }
-
- /**
- * Sets the URL for this request
- *
- * If the URL has userinfo part (username & password) these will be removed
- * and converted to auth data. If the URL does not have a path component,
- * that will be set to '/'.
- *
- * @param string|Net_URL2 Request URL
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- */
- public function setUrl($url)
- {
- if (is_string($url)) {
- $url = new Net_URL2(
- $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets'])
- );
- }
- if (!$url instanceof Net_URL2) {
- throw new HTTP_Request2_LogicException(
- 'Parameter is not a valid HTTP URL',
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- // URL contains username / password?
- if ($url->getUserinfo()) {
- $username = $url->getUser();
- $password = $url->getPassword();
- $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');
- $url->setUserinfo('');
- }
- if ('' == $url->getPath()) {
- $url->setPath('/');
- }
- $this->url = $url;
-
- return $this;
- }
-
- /**
- * Returns the request URL
- *
- * @return Net_URL2
- */
- public function getUrl()
- {
- return $this->url;
- }
-
- /**
- * Sets the request method
- *
- * @param string
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException if the method name is invalid
- */
- public function setMethod($method)
- {
- // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1
- if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {
- throw new HTTP_Request2_LogicException(
- "Invalid request method '{$method}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $this->method = $method;
-
- return $this;
- }
-
- /**
- * Returns the request method
- *
- * @return string
- */
- public function getMethod()
- {
- return $this->method;
- }
-
- /**
- * Sets the configuration parameter(s)
- *
- * The following parameters are available:
- * <ul>
- * <li> 'adapter' - adapter to use (string)</li>
- * <li> 'connect_timeout' - Connection timeout in seconds (integer)</li>
- * <li> 'timeout' - Total number of seconds a request can take.
- * Use 0 for no limit, should be greater than
- * 'connect_timeout' if set (integer)</li>
- * <li> 'use_brackets' - Whether to append [] to array variable names (bool)</li>
- * <li> 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)</li>
- * <li> 'buffer_size' - Buffer size to use for reading and writing (int)</li>
- * <li> 'store_body' - Whether to store response body in response object.
- * Set to false if receiving a huge response and
- * using an Observer to save it (boolean)</li>
- * <li> 'proxy_host' - Proxy server host (string)</li>
- * <li> 'proxy_port' - Proxy server port (integer)</li>
- * <li> 'proxy_user' - Proxy auth username (string)</li>
- * <li> 'proxy_password' - Proxy auth password (string)</li>
- * <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>
- * <li> 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)</li>
- * <li> 'ssl_verify_host' - Whether to check that Common Name in SSL
- * certificate matches host name (bool)</li>
- * <li> 'ssl_cafile' - Cerificate Authority file to verify the peer
- * with (use with 'ssl_verify_peer') (string)</li>
- * <li> 'ssl_capath' - Directory holding multiple Certificate
- * Authority files (string)</li>
- * <li> 'ssl_local_cert' - Name of a file containing local cerificate (string)</li>
- * <li> 'ssl_passphrase' - Passphrase with which local certificate
- * was encoded (string)</li>
- * <li> 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6
- * in using URL without query string in digest
- * authentication (boolean)</li>
- * <li> 'follow_redirects' - Whether to automatically follow HTTP Redirects (boolean)</li>
- * <li> 'max_redirects' - Maximum number of redirects to follow (integer)</li>
- * <li> 'strict_redirects' - Whether to keep request method on redirects via status 301 and
- * 302 (true, needed for compatibility with RFC 2616)
- * or switch to GET (false, needed for compatibility with most
- * browsers) (boolean)</li>
- * </ul>
- *
- * @param string|array configuration parameter name or array
- * ('parameter name' => 'parameter value')
- * @param mixed parameter value if $nameOrConfig is not an array
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException If the parameter is unknown
- */
- public function setConfig($nameOrConfig, $value = null)
- {
- if (is_array($nameOrConfig)) {
- foreach ($nameOrConfig as $name => $value) {
- $this->setConfig($name, $value);
- }
-
- } else {
- if (!array_key_exists($nameOrConfig, $this->config)) {
- throw new HTTP_Request2_LogicException(
- "Unknown configuration parameter '{$nameOrConfig}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $this->config[$nameOrConfig] = $value;
- }
-
- return $this;
- }
-
- /**
- * Returns the value(s) of the configuration parameter(s)
- *
- * @param string parameter name
- * @return mixed value of $name parameter, array of all configuration
- * parameters if $name is not given
- * @throws HTTP_Request2_LogicException If the parameter is unknown
- */
- public function getConfig($name = null)
- {
- if (null === $name) {
- return $this->config;
- } elseif (!array_key_exists($name, $this->config)) {
- throw new HTTP_Request2_LogicException(
- "Unknown configuration parameter '{$name}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- return $this->config[$name];
- }
-
- /**
- * Sets the autentification data
- *
- * @param string user name
- * @param string password
- * @param string authentication scheme
- * @return HTTP_Request2
- */
- public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)
- {
- if (empty($user)) {
- $this->auth = null;
- } else {
- $this->auth = array(
- 'user' => (string)$user,
- 'password' => (string)$password,
- 'scheme' => $scheme
- );
- }
-
- return $this;
- }
-
- /**
- * Returns the authentication data
- *
- * The array has the keys 'user', 'password' and 'scheme', where 'scheme'
- * is one of the HTTP_Request2::AUTH_* constants.
- *
- * @return array
- */
- public function getAuth()
- {
- return $this->auth;
- }
-
- /**
- * Sets request header(s)
- *
- * The first parameter may be either a full header string 'header: value' or
- * header name. In the former case $value parameter is ignored, in the latter
- * the header's value will either be set to $value or the header will be
- * removed if $value is null. The first parameter can also be an array of
- * headers, in that case method will be called recursively.
- *
- * Note that headers are treated case insensitively as per RFC 2616.
- *
- * <code>
- * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'
- * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'
- * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'
- * $req->setHeader('FOO'); // removes 'Foo' header from request
- * </code>
- *
- * @param string|array header name, header string ('Header: value')
- * or an array of headers
- * @param string|array|null header value if $name is not an array,
- * header will be removed if value is null
- * @param bool whether to replace previous header with the
- * same name or append to its value
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- */
- public function setHeader($name, $value = null, $replace = true)
- {
- if (is_array($name)) {
- foreach ($name as $k => $v) {
- if (is_string($k)) {
- $this->setHeader($k, $v, $replace);
- } else {
- $this->setHeader($v, null, $replace);
- }
- }
- } else {
- if (null === $value && strpos($name, ':')) {
- list($name, $value) = array_map('trim', explode(':', $name, 2));
- }
- // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2
- if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {
- throw new HTTP_Request2_LogicException(
- "Invalid header name '{$name}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- // Header names are case insensitive anyway
- $name = strtolower($name);
- if (null === $value) {
- unset($this->headers[$name]);
-
- } else {
- if (is_array($value)) {
- $value = implode(', ', array_map('trim', $value));
- } elseif (is_string($value)) {
- $value = trim($value);
- }
- if (!isset($this->headers[$name]) || $replace) {
- $this->headers[$name] = $value;
- } else {
- $this->headers[$name] .= ', ' . $value;
- }
- }
- }
-
- return $this;
- }
-
- /**
- * Returns the request headers
- *
- * The array is of the form ('header name' => 'header value'), header names
- * are lowercased
- *
- * @return array
- */
- public function getHeaders()
- {
- return $this->headers;
- }
-
- /**
- * Adds a cookie to the request
- *
- * If the request does not have a CookieJar object set, this method simply
- * appends a cookie to "Cookie:" header.
- *
- * If a CookieJar object is available, the cookie is stored in that object.
- * Data from request URL will be used for setting its 'domain' and 'path'
- * parameters, 'expires' and 'secure' will be set to null and false,
- * respectively. If you need further control, use CookieJar's methods.
- *
- * @param string cookie name
- * @param string cookie value
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- * @see setCookieJar()
- */
- public function addCookie($name, $value)
- {
- if (!empty($this->cookieJar)) {
- $this->cookieJar->store(array('name' => $name, 'value' => $value),
- $this->url);
-
- } else {
- $cookie = $name . '=' . $value;
- if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {
- throw new HTTP_Request2_LogicException(
- "Invalid cookie: '{$cookie}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';
- $this->setHeader('cookie', $cookies . $cookie);
- }
-
- return $this;
- }
-
- /**
- * Sets the request body
- *
- * If you provide file pointer rather than file name, it should support
- * fstat() and rewind() operations.
- *
- * @param string|resource|HTTP_Request2_MultipartBody Either a string
- * with the body or filename containing body or pointer to
- * an open file or object with multipart body data
- * @param bool Whether first parameter is a filename
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- */
- public function setBody($body, $isFilename = false)
- {
- if (!$isFilename && !is_resource($body)) {
- if (!$body instanceof HTTP_Request2_MultipartBody) {
- $this->body = (string)$body;
- } else {
- $this->body = $body;
- }
- } else {
- $fileData = $this->fopenWrapper($body, empty($this->headers['content-type']));
- $this->body = $fileData['fp'];
- if (empty($this->headers['content-type'])) {
- $this->setHeader('content-type', $fileData['type']);
- }
- }
- $this->postParams = $this->uploads = array();
-
- return $this;
- }
-
- /**
- * Returns the request body
- *
- * @return string|resource|HTTP_Request2_MultipartBody
- */
- public function getBody()
- {
- if (self::METHOD_POST == $this->method &&
- (!empty($this->postParams) || !empty($this->uploads))
- ) {
- if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) {
- $body = http_build_query($this->postParams, '', '&');
- if (!$this->getConfig('use_brackets')) {
- $body = preg_replace('/%5B\d+%5D=/', '=', $body);
- }
- // support RFC 3986 by not encoding '~' symbol (request #15368)
- return str_replace('%7E', '~', $body);
-
- } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) {
- require_once 'HTTP/Request2/MultipartBody.php';
- return new HTTP_Request2_MultipartBody(
- $this->postParams, $this->uploads, $this->getConfig('use_brackets')
- );
- }
- }
- return $this->body;
- }
-
- /**
- * Adds a file to form-based file upload
- *
- * Used to emulate file upload via a HTML form. The method also sets
- * Content-Type of HTTP request to 'multipart/form-data'.
- *
- * If you just want to send the contents of a file as the body of HTTP
- * request you should use setBody() method.
- *
- * If you provide file pointers rather than file names, they should support
- * fstat() and rewind() operations.
- *
- * @param string name of file-upload field
- * @param string|resource|array full name of local file, pointer to
- * open file or an array of files
- * @param string filename to send in the request
- * @param string content-type of file being uploaded
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- */
- public function addUpload($fieldName, $filename, $sendFilename = null,
- $contentType = null)
- {
- if (!is_array($filename)) {
- $fileData = $this->fopenWrapper($filename, empty($contentType));
- $this->uploads[$fieldName] = array(
- 'fp' => $fileData['fp'],
- 'filename' => !empty($sendFilename)? $sendFilename
- :(is_string($filename)? basename($filename): 'anonymous.blob') ,
- 'size' => $fileData['size'],
- 'type' => empty($contentType)? $fileData['type']: $contentType
- );
- } else {
- $fps = $names = $sizes = $types = array();
- foreach ($filename as $f) {
- if (!is_array($f)) {
- $f = array($f);
- }
- $fileData = $this->fopenWrapper($f[0], empty($f[2]));
- $fps[] = $fileData['fp'];
- $names[] = !empty($f[1])? $f[1]
- :(is_string($f[0])? basename($f[0]): 'anonymous.blob');
- $sizes[] = $fileData['size'];
- $types[] = empty($f[2])? $fileData['type']: $f[2];
- }
- $this->uploads[$fieldName] = array(
- 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types
- );
- }
- if (empty($this->headers['content-type']) ||
- 'application/x-www-form-urlencoded' == $this->headers['content-type']
- ) {
- $this->setHeader('content-type', 'multipart/form-data');
- }
-
- return $this;
- }
-
- /**
- * Adds POST parameter(s) to the request.
- *
- * @param string|array parameter name or array ('name' => 'value')
- * @param mixed parameter value (can be an array)
- * @return HTTP_Request2
- */
- public function addPostParameter($name, $value = null)
- {
- if (!is_array($name)) {
- $this->postParams[$name] = $value;
- } else {
- foreach ($name as $k => $v) {
- $this->addPostParameter($k, $v);
- }
- }
- if (empty($this->headers['content-type'])) {
- $this->setHeader('content-type', 'application/x-www-form-urlencoded');
- }
-
- return $this;
- }
-
- /**
- * Attaches a new observer
- *
- * @param SplObserver
- */
- public function attach(SplObserver $observer)
- {
- foreach ($this->observers as $attached) {
- if ($attached === $observer) {
- return;
- }
- }
- $this->observers[] = $observer;
- }
-
- /**
- * Detaches an existing observer
- *
- * @param SplObserver
- */
- public function detach(SplObserver $observer)
- {
- foreach ($this->observers as $key => $attached) {
- if ($attached === $observer) {
- unset($this->observers[$key]);
- return;
- }
- }
- }
-
- /**
- * Notifies all observers
- */
- public function notify()
- {
- foreach ($this->observers as $observer) {
- $observer->update($this);
- }
- }
-
- /**
- * Sets the last event
- *
- * Adapters should use this method to set the current state of the request
- * and notify the observers.
- *
- * @param string event name
- * @param mixed event data
- */
- public function setLastEvent($name, $data = null)
- {
- $this->lastEvent = array(
- 'name' => $name,
- 'data' => $data
- );
- $this->notify();
- }
-
- /**
- * Returns the last event
- *
- * Observers should use this method to access the last change in request.
- * The following event names are possible:
- * <ul>
- * <li>'connect' - after connection to remote server,
- * data is the destination (string)</li>
- * <li>'disconnect' - after disconnection from server</li>
- * <li>'sentHeaders' - after sending the request headers,
- * data is the headers sent (string)</li>
- * <li>'sentBodyPart' - after sending a part of the request body,
- * data is the length of that part (int)</li>
- * <li>'sentBody' - after sending the whole request body,
- * data is request body length (int)</li>
- * <li>'receivedHeaders' - after receiving the response headers,
- * data is HTTP_Request2_Response object</li>
- * <li>'receivedBodyPart' - after receiving a part of the response
- * body, data is that part (string)</li>
- * <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still
- * encoded by Content-Encoding</li>
- * <li>'receivedBody' - after receiving the complete response
- * body, data is HTTP_Request2_Response object</li>
- * </ul>
- * Different adapters may not send all the event types. Mock adapter does
- * not send any events to the observers.
- *
- * @return array The array has two keys: 'name' and 'data'
- */
- public function getLastEvent()
- {
- return $this->lastEvent;
- }
-
- /**
- * Sets the adapter used to actually perform the request
- *
- * You can pass either an instance of a class implementing HTTP_Request2_Adapter
- * or a class name. The method will only try to include a file if the class
- * name starts with HTTP_Request2_Adapter_, it will also try to prepend this
- * prefix to the class name if it doesn't contain any underscores, so that
- * <code>
- * $request->setAdapter('curl');
- * </code>
- * will work.
- *
- * @param string|HTTP_Request2_Adapter
- * @return HTTP_Request2
- * @throws HTTP_Request2_LogicException
- */
- public function setAdapter($adapter)
- {
- if (is_string($adapter)) {
- if (!class_exists($adapter, false)) {
- if (false === strpos($adapter, '_')) {
- $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);
- }
- if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) {
- include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';
- }
- if (!class_exists($adapter, false)) {
- throw new HTTP_Request2_LogicException(
- "Class {$adapter} not found",
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- }
- $adapter = new $adapter;
- }
- if (!$adapter instanceof HTTP_Request2_Adapter) {
- throw new HTTP_Request2_LogicException(
- 'Parameter is not a HTTP request adapter',
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $this->adapter = $adapter;
-
- return $this;
- }
-
- /**
- * Sets the cookie jar
- *
- * A cookie jar is used to maintain cookies across HTTP requests and
- * responses. Cookies from jar will be automatically added to the request
- * headers based on request URL.
- *
- * @param HTTP_Request2_CookieJar|bool Existing CookieJar object, true to
- * create a new one, false to remove
- */
- public function setCookieJar($jar = true)
- {
- if (!class_exists('HTTP_Request2_CookieJar', false)) {
- require_once 'HTTP/Request2/CookieJar.php';
- }
-
- if ($jar instanceof HTTP_Request2_CookieJar) {
- $this->cookieJar = $jar;
- } elseif (true === $jar) {
- $this->cookieJar = new HTTP_Request2_CookieJar();
- } elseif (!$jar) {
- $this->cookieJar = null;
- } else {
- throw new HTTP_Request2_LogicException(
- 'Invalid parameter passed to setCookieJar()',
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
-
- return $this;
- }
-
- /**
- * Returns current CookieJar object or null if none
- *
- * @return HTTP_Request2_CookieJar|null
- */
- public function getCookieJar()
- {
- return $this->cookieJar;
- }
-
- /**
- * Sends the request and returns the response
- *
- * @throws HTTP_Request2_Exception
- * @return HTTP_Request2_Response
- */
- public function send()
- {
- // Sanity check for URL
- if (!$this->url instanceof Net_URL2
- || !$this->url->isAbsolute()
- || !in_array(strtolower($this->url->getScheme()), array('https', 'http'))
- ) {
- throw new HTTP_Request2_LogicException(
- 'HTTP_Request2 needs an absolute HTTP(S) request URL, '
- . ($this->url instanceof Net_URL2
- ? "'" . $this->url->__toString() . "'" : 'none')
- . ' given',
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- if (empty($this->adapter)) {
- $this->setAdapter($this->getConfig('adapter'));
- }
- // magic_quotes_runtime may break file uploads and chunked response
- // processing; see bug #4543. Don't use ini_get() here; see bug #16440.
- if ($magicQuotes = get_magic_quotes_runtime()) {
- set_magic_quotes_runtime(false);
- }
- // force using single byte encoding if mbstring extension overloads
- // strlen() and substr(); see bug #1781, bug #10605
- if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
- $oldEncoding = mb_internal_encoding();
- mb_internal_encoding('iso-8859-1');
- }
-
- try {
- $response = $this->adapter->sendRequest($this);
- } catch (Exception $e) {
- }
- // cleanup in either case (poor man's "finally" clause)
- if ($magicQuotes) {
- set_magic_quotes_runtime(true);
- }
- if (!empty($oldEncoding)) {
- mb_internal_encoding($oldEncoding);
- }
- // rethrow the exception
- if (!empty($e)) {
- throw $e;
- }
- return $response;
- }
-
- /**
- * Wrapper around fopen()/fstat() used by setBody() and addUpload()
- *
- * @param string|resource file name or pointer to open file
- * @param bool whether to try autodetecting MIME type of file,
- * will only work if $file is a filename, not pointer
- * @return array array('fp' => file pointer, 'size' => file size, 'type' => MIME type)
- * @throws HTTP_Request2_LogicException
- */
- protected function fopenWrapper($file, $detectType = false)
- {
- if (!is_string($file) && !is_resource($file)) {
- throw new HTTP_Request2_LogicException(
- "Filename or file pointer resource expected",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $fileData = array(
- 'fp' => is_string($file)? null: $file,
- 'type' => 'application/octet-stream',
- 'size' => 0
- );
- if (is_string($file)) {
- $track = @ini_set('track_errors', 1);
- if (!($fileData['fp'] = @fopen($file, 'rb'))) {
- $e = new HTTP_Request2_LogicException(
- $php_errormsg, HTTP_Request2_Exception::READ_ERROR
- );
- }
- @ini_set('track_errors', $track);
- if (isset($e)) {
- throw $e;
- }
- if ($detectType) {
- $fileData['type'] = self::detectMimeType($file);
- }
- }
- if (!($stat = fstat($fileData['fp']))) {
- throw new HTTP_Request2_LogicException(
- "fstat() call failed", HTTP_Request2_Exception::READ_ERROR
- );
- }
- $fileData['size'] = $stat['size'];
-
- return $fileData;
- }
-
- /**
- * Tries to detect MIME type of a file
- *
- * The method will try to use fileinfo extension if it is available,
- * deprecated mime_content_type() function in the other case. If neither
- * works, default 'application/octet-stream' MIME type is returned
- *
- * @param string filename
- * @return string file MIME type
- */
- protected static function detectMimeType($filename)
- {
- // finfo extension from PECL available
- if (function_exists('finfo_open')) {
- if (!isset(self::$_fileinfoDb)) {
- self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);
- }
- if (self::$_fileinfoDb) {
- $info = finfo_file(self::$_fileinfoDb, $filename);
- }
- }
- // (deprecated) mime_content_type function available
- if (empty($info) && function_exists('mime_content_type')) {
- return mime_content_type($filename);
- }
- return empty($info)? 'application/octet-stream': $info;
- }
-}
-?>
diff --git a/lib/ext/HTTP/Request2/Adapter.php b/lib/ext/HTTP/Request2/Adapter.php
deleted file mode 100644
index 56d81a9..0000000
--- a/lib/ext/HTTP/Request2/Adapter.php
+++ /dev/null
@@ -1,154 +0,0 @@
-<?php
-/**
- * Base class for HTTP_Request2 adapters
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Adapter.php 308322 2011-02-14 13:58:03Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Class representing a HTTP response
- */
-require_once 'HTTP/Request2/Response.php';
-
-/**
- * Base class for HTTP_Request2 adapters
- *
- * HTTP_Request2 class itself only defines methods for aggregating the request
- * data, all actual work of sending the request to the remote server and
- * receiving its response is performed by adapters.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- */
-abstract class HTTP_Request2_Adapter
-{
- /**
- * A list of methods that MUST NOT have a request body, per RFC 2616
- * @var array
- */
- protected static $bodyDisallowed = array('TRACE');
-
- /**
- * Methods having defined semantics for request body
- *
- * Content-Length header (indicating that the body follows, section 4.3 of
- * RFC 2616) will be sent for these methods even if no body was added
- *
- * @var array
- * @link http://pear.php.net/bugs/bug.php?id=12900
- * @link http://pear.php.net/bugs/bug.php?id=14740
- */
- protected static $bodyRequired = array('POST', 'PUT');
-
- /**
- * Request being sent
- * @var HTTP_Request2
- */
- protected $request;
-
- /**
- * Request body
- * @var string|resource|HTTP_Request2_MultipartBody
- * @see HTTP_Request2::getBody()
- */
- protected $requestBody;
-
- /**
- * Length of the request body
- * @var integer
- */
- protected $contentLength;
-
- /**
- * Sends request to the remote server and returns its response
- *
- * @param HTTP_Request2
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- abstract public function sendRequest(HTTP_Request2 $request);
-
- /**
- * Calculates length of the request body, adds proper headers
- *
- * @param array associative array of request headers, this method will
- * add proper 'Content-Length' and 'Content-Type' headers
- * to this array (or remove them if not needed)
- */
- protected function calculateRequestLength(&$headers)
- {
- $this->requestBody = $this->request->getBody();
-
- if (is_string($this->requestBody)) {
- $this->contentLength = strlen($this->requestBody);
- } elseif (is_resource($this->requestBody)) {
- $stat = fstat($this->requestBody);
- $this->contentLength = $stat['size'];
- rewind($this->requestBody);
- } else {
- $this->contentLength = $this->requestBody->getLength();
- $headers['content-type'] = 'multipart/form-data; boundary=' .
- $this->requestBody->getBoundary();
- $this->requestBody->rewind();
- }
-
- if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
- 0 == $this->contentLength
- ) {
- // No body: send a Content-Length header nonetheless (request #12900),
- // but do that only for methods that require a body (bug #14740)
- if (in_array($this->request->getMethod(), self::$bodyRequired)) {
- $headers['content-length'] = 0;
- } else {
- unset($headers['content-length']);
- // if the method doesn't require a body and doesn't have a
- // body, don't send a Content-Type header. (request #16799)
- unset($headers['content-type']);
- }
- } else {
- if (empty($headers['content-type'])) {
- $headers['content-type'] = 'application/x-www-form-urlencoded';
- }
- $headers['content-length'] = $this->contentLength;
- }
- }
-}
-?>
diff --git a/lib/ext/HTTP/Request2/Adapter/Curl.php b/lib/ext/HTTP/Request2/Adapter/Curl.php
deleted file mode 100644
index 82d227f..0000000
--- a/lib/ext/HTTP/Request2/Adapter/Curl.php
+++ /dev/null
@@ -1,560 +0,0 @@
-<?php
-/**
- * Adapter for HTTP_Request2 wrapping around cURL extension
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Curl.php 310800 2011-05-06 07:29:56Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Base class for HTTP_Request2 adapters
- */
-require_once 'HTTP/Request2/Adapter.php';
-
-/**
- * Adapter for HTTP_Request2 wrapping around cURL extension
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- */
-class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
-{
- /**
- * Mapping of header names to cURL options
- * @var array
- */
- protected static $headerMap = array(
- 'accept-encoding' => CURLOPT_ENCODING,
- 'cookie' => CURLOPT_COOKIE,
- 'referer' => CURLOPT_REFERER,
- 'user-agent' => CURLOPT_USERAGENT
- );
-
- /**
- * Mapping of SSL context options to cURL options
- * @var array
- */
- protected static $sslContextMap = array(
- 'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,
- 'ssl_cafile' => CURLOPT_CAINFO,
- 'ssl_capath' => CURLOPT_CAPATH,
- 'ssl_local_cert' => CURLOPT_SSLCERT,
- 'ssl_passphrase' => CURLOPT_SSLCERTPASSWD
- );
-
- /**
- * Mapping of CURLE_* constants to Exception subclasses and error codes
- * @var array
- */
- protected static $errorMap = array(
- CURLE_UNSUPPORTED_PROTOCOL => array('HTTP_Request2_MessageException',
- HTTP_Request2_Exception::NON_HTTP_REDIRECT),
- CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),
- CURLE_COULDNT_RESOLVE_HOST => array('HTTP_Request2_ConnectionException'),
- CURLE_COULDNT_CONNECT => array('HTTP_Request2_ConnectionException'),
- // error returned from write callback
- CURLE_WRITE_ERROR => array('HTTP_Request2_MessageException',
- HTTP_Request2_Exception::NON_HTTP_REDIRECT),
- CURLE_OPERATION_TIMEOUTED => array('HTTP_Request2_MessageException',
- HTTP_Request2_Exception::TIMEOUT),
- CURLE_HTTP_RANGE_ERROR => array('HTTP_Request2_MessageException'),
- CURLE_SSL_CONNECT_ERROR => array('HTTP_Request2_ConnectionException'),
- CURLE_LIBRARY_NOT_FOUND => array('HTTP_Request2_LogicException',
- HTTP_Request2_Exception::MISCONFIGURATION),
- CURLE_FUNCTION_NOT_FOUND => array('HTTP_Request2_LogicException',
- HTTP_Request2_Exception::MISCONFIGURATION),
- CURLE_ABORTED_BY_CALLBACK => array('HTTP_Request2_MessageException',
- HTTP_Request2_Exception::NON_HTTP_REDIRECT),
- CURLE_TOO_MANY_REDIRECTS => array('HTTP_Request2_MessageException',
- HTTP_Request2_Exception::TOO_MANY_REDIRECTS),
- CURLE_SSL_PEER_CERTIFICATE => array('HTTP_Request2_ConnectionException'),
- CURLE_GOT_NOTHING => array('HTTP_Request2_MessageException'),
- CURLE_SSL_ENGINE_NOTFOUND => array('HTTP_Request2_LogicException',
- HTTP_Request2_Exception::MISCONFIGURATION),
- CURLE_SSL_ENGINE_SETFAILED => array('HTTP_Request2_LogicException',
- HTTP_Request2_Exception::MISCONFIGURATION),
- CURLE_SEND_ERROR => array('HTTP_Request2_MessageException'),
- CURLE_RECV_ERROR => array('HTTP_Request2_MessageException'),
- CURLE_SSL_CERTPROBLEM => array('HTTP_Request2_LogicException',
- HTTP_Request2_Exception::INVALID_ARGUMENT),
- CURLE_SSL_CIPHER => array('HTTP_Request2_ConnectionException'),
- CURLE_SSL_CACERT => array('HTTP_Request2_ConnectionException'),
- CURLE_BAD_CONTENT_ENCODING => array('HTTP_Request2_MessageException'),
- );
-
- /**
- * Response being received
- * @var HTTP_Request2_Response
- */
- protected $response;
-
- /**
- * Whether 'sentHeaders' event was sent to observers
- * @var boolean
- */
- protected $eventSentHeaders = false;
-
- /**
- * Whether 'receivedHeaders' event was sent to observers
- * @var boolean
- */
- protected $eventReceivedHeaders = false;
-
- /**
- * Position within request body
- * @var integer
- * @see callbackReadBody()
- */
- protected $position = 0;
-
- /**
- * Information about last transfer, as returned by curl_getinfo()
- * @var array
- */
- protected $lastInfo;
-
- /**
- * Creates a subclass of HTTP_Request2_Exception from curl error data
- *
- * @param resource curl handle
- * @return HTTP_Request2_Exception
- */
- protected static function wrapCurlError($ch)
- {
- $nativeCode = curl_errno($ch);
- $message = 'Curl error: ' . curl_error($ch);
- if (!isset(self::$errorMap[$nativeCode])) {
- return new HTTP_Request2_Exception($message, 0, $nativeCode);
- } else {
- $class = self::$errorMap[$nativeCode][0];
- $code = empty(self::$errorMap[$nativeCode][1])
- ? 0 : self::$errorMap[$nativeCode][1];
- return new $class($message, $code, $nativeCode);
- }
- }
-
- /**
- * Sends request to the remote server and returns its response
- *
- * @param HTTP_Request2
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- public function sendRequest(HTTP_Request2 $request)
- {
- if (!extension_loaded('curl')) {
- throw new HTTP_Request2_LogicException(
- 'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION
- );
- }
-
- $this->request = $request;
- $this->response = null;
- $this->position = 0;
- $this->eventSentHeaders = false;
- $this->eventReceivedHeaders = false;
-
- try {
- if (false === curl_exec($ch = $this->createCurlHandle())) {
- $e = self::wrapCurlError($ch);
- }
- } catch (Exception $e) {
- }
- if (isset($ch)) {
- $this->lastInfo = curl_getinfo($ch);
- curl_close($ch);
- }
-
- $response = $this->response;
- unset($this->request, $this->requestBody, $this->response);
-
- if (!empty($e)) {
- throw $e;
- }
-
- if ($jar = $request->getCookieJar()) {
- $jar->addCookiesFromResponse($response, $request->getUrl());
- }
-
- if (0 < $this->lastInfo['size_download']) {
- $request->setLastEvent('receivedBody', $response);
- }
- return $response;
- }
-
- /**
- * Returns information about last transfer
- *
- * @return array associative array as returned by curl_getinfo()
- */
- public function getInfo()
- {
- return $this->lastInfo;
- }
-
- /**
- * Creates a new cURL handle and populates it with data from the request
- *
- * @return resource a cURL handle, as created by curl_init()
- * @throws HTTP_Request2_LogicException
- */
- protected function createCurlHandle()
- {
- $ch = curl_init();
-
- curl_setopt_array($ch, array(
- // setup write callbacks
- CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),
- CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'),
- // buffer size
- CURLOPT_BUFFERSIZE => $this->request->getConfig('buffer_size'),
- // connection timeout
- CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),
- // save full outgoing headers, in case someone is interested
- CURLINFO_HEADER_OUT => true,
- // request url
- CURLOPT_URL => $this->request->getUrl()->getUrl()
- ));
-
- // set up redirects
- if (!$this->request->getConfig('follow_redirects')) {
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
- } else {
- if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {
- throw new HTTP_Request2_LogicException(
- 'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',
- HTTP_Request2_Exception::MISCONFIGURATION
- );
- }
- curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));
- // limit redirects to http(s), works in 5.2.10+
- if (defined('CURLOPT_REDIR_PROTOCOLS')) {
- curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
- }
- // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571
- if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {
- curl_setopt($ch, CURLOPT_POSTREDIR, 3);
- }
- }
-
- // request timeout
- if ($timeout = $this->request->getConfig('timeout')) {
- curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
- }
-
- // set HTTP version
- switch ($this->request->getConfig('protocol_version')) {
- case '1.0':
- curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
- break;
- case '1.1':
- curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
- }
-
- // set request method
- switch ($this->request->getMethod()) {
- case HTTP_Request2::METHOD_GET:
- curl_setopt($ch, CURLOPT_HTTPGET, true);
- break;
- case HTTP_Request2::METHOD_POST:
- curl_setopt($ch, CURLOPT_POST, true);
- break;
- case HTTP_Request2::METHOD_HEAD:
- curl_setopt($ch, CURLOPT_NOBODY, true);
- break;
- case HTTP_Request2::METHOD_PUT:
- curl_setopt($ch, CURLOPT_UPLOAD, true);
- break;
- default:
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
- }
-
- // set proxy, if needed
- if ($host = $this->request->getConfig('proxy_host')) {
- if (!($port = $this->request->getConfig('proxy_port'))) {
- throw new HTTP_Request2_LogicException(
- 'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);
- if ($user = $this->request->getConfig('proxy_user')) {
- curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' .
- $this->request->getConfig('proxy_password'));
- switch ($this->request->getConfig('proxy_auth_scheme')) {
- case HTTP_Request2::AUTH_BASIC:
- curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
- break;
- case HTTP_Request2::AUTH_DIGEST:
- curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
- }
- }
- }
-
- // set authentication data
- if ($auth = $this->request->getAuth()) {
- curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
- switch ($auth['scheme']) {
- case HTTP_Request2::AUTH_BASIC:
- curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
- break;
- case HTTP_Request2::AUTH_DIGEST:
- curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
- }
- }
-
- // set SSL options
- foreach ($this->request->getConfig() as $name => $value) {
- if ('ssl_verify_host' == $name && null !== $value) {
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
- } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
- curl_setopt($ch, self::$sslContextMap[$name], $value);
- }
- }
-
- $headers = $this->request->getHeaders();
- // make cURL automagically send proper header
- if (!isset($headers['accept-encoding'])) {
- $headers['accept-encoding'] = '';
- }
-
- if (($jar = $this->request->getCookieJar())
- && ($cookies = $jar->getMatching($this->request->getUrl(), true))
- ) {
- $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
- }
-
- // set headers having special cURL keys
- foreach (self::$headerMap as $name => $option) {
- if (isset($headers[$name])) {
- curl_setopt($ch, $option, $headers[$name]);
- unset($headers[$name]);
- }
- }
-
- $this->calculateRequestLength($headers);
- if (isset($headers['content-length'])) {
- $this->workaroundPhpBug47204($ch, $headers);
- }
-
- // set headers not having special keys
- $headersFmt = array();
- foreach ($headers as $name => $value) {
- $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
- $headersFmt[] = $canonicalName . ': ' . $value;
- }
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);
-
- return $ch;
- }
-
- /**
- * Workaround for PHP bug #47204 that prevents rewinding request body
- *
- * The workaround consists of reading the entire request body into memory
- * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large
- * file uploads, use Socket adapter instead.
- *
- * @param resource cURL handle
- * @param array Request headers
- */
- protected function workaroundPhpBug47204($ch, &$headers)
- {
- // no redirects, no digest auth -> probably no rewind needed
- if (!$this->request->getConfig('follow_redirects')
- && (!($auth = $this->request->getAuth())
- || HTTP_Request2::AUTH_DIGEST != $auth['scheme'])
- ) {
- curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));
-
- // rewind may be needed, read the whole body into memory
- } else {
- if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {
- $this->requestBody = $this->requestBody->__toString();
-
- } elseif (is_resource($this->requestBody)) {
- $fp = $this->requestBody;
- $this->requestBody = '';
- while (!feof($fp)) {
- $this->requestBody .= fread($fp, 16384);
- }
- }
- // curl hangs up if content-length is present
- unset($headers['content-length']);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);
- }
- }
-
- /**
- * Callback function called by cURL for reading the request body
- *
- * @param resource cURL handle
- * @param resource file descriptor (not used)
- * @param integer maximum length of data to return
- * @return string part of the request body, up to $length bytes
- */
- protected function callbackReadBody($ch, $fd, $length)
- {
- if (!$this->eventSentHeaders) {
- $this->request->setLastEvent(
- 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
- );
- $this->eventSentHeaders = true;
- }
- if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
- 0 == $this->contentLength || $this->position >= $this->contentLength
- ) {
- return '';
- }
- if (is_string($this->requestBody)) {
- $string = substr($this->requestBody, $this->position, $length);
- } elseif (is_resource($this->requestBody)) {
- $string = fread($this->requestBody, $length);
- } else {
- $string = $this->requestBody->read($length);
- }
- $this->request->setLastEvent('sentBodyPart', strlen($string));
- $this->position += strlen($string);
- return $string;
- }
-
- /**
- * Callback function called by cURL for saving the response headers
- *
- * @param resource cURL handle
- * @param string response header (with trailing CRLF)
- * @return integer number of bytes saved
- * @see HTTP_Request2_Response::parseHeaderLine()
- */
- protected function callbackWriteHeader($ch, $string)
- {
- // we may receive a second set of headers if doing e.g. digest auth
- if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
- // don't bother with 100-Continue responses (bug #15785)
- if (!$this->eventSentHeaders ||
- $this->response->getStatus() >= 200
- ) {
- $this->request->setLastEvent(
- 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
- );
- }
- $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
- // if body wasn't read by a callback, send event with total body size
- if ($upload > $this->position) {
- $this->request->setLastEvent(
- 'sentBodyPart', $upload - $this->position
- );
- $this->position = $upload;
- }
- if ($upload && (!$this->eventSentHeaders
- || $this->response->getStatus() >= 200)
- ) {
- $this->request->setLastEvent('sentBody', $upload);
- }
- $this->eventSentHeaders = true;
- // we'll need a new response object
- if ($this->eventReceivedHeaders) {
- $this->eventReceivedHeaders = false;
- $this->response = null;
- }
- }
- if (empty($this->response)) {
- $this->response = new HTTP_Request2_Response(
- $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
- );
- } else {
- $this->response->parseHeaderLine($string);
- if ('' == trim($string)) {
- // don't bother with 100-Continue responses (bug #15785)
- if (200 <= $this->response->getStatus()) {
- $this->request->setLastEvent('receivedHeaders', $this->response);
- }
-
- if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {
- $redirectUrl = new Net_URL2($this->response->getHeader('location'));
-
- // for versions lower than 5.2.10, check the redirection URL protocol
- if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()
- && !in_array($redirectUrl->getScheme(), array('http', 'https'))
- ) {
- return -1;
- }
-
- if ($jar = $this->request->getCookieJar()) {
- $jar->addCookiesFromResponse($this->response, $this->request->getUrl());
- if (!$redirectUrl->isAbsolute()) {
- $redirectUrl = $this->request->getUrl()->resolve($redirectUrl);
- }
- if ($cookies = $jar->getMatching($redirectUrl, true)) {
- curl_setopt($ch, CURLOPT_COOKIE, $cookies);
- }
- }
- }
- $this->eventReceivedHeaders = true;
- }
- }
- return strlen($string);
- }
-
- /**
- * Callback function called by cURL for saving the response body
- *
- * @param resource cURL handle (not used)
- * @param string part of the response body
- * @return integer number of bytes saved
- * @see HTTP_Request2_Response::appendBody()
- */
- protected function callbackWriteBody($ch, $string)
- {
- // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if
- // response doesn't start with proper HTTP status line (see bug #15716)
- if (empty($this->response)) {
- throw new HTTP_Request2_MessageException(
- "Malformed response: {$string}",
- HTTP_Request2_Exception::MALFORMED_RESPONSE
- );
- }
- if ($this->request->getConfig('store_body')) {
- $this->response->appendBody($string);
- }
- $this->request->setLastEvent('receivedBodyPart', $string);
- return strlen($string);
- }
-}
-?>
diff --git a/lib/ext/HTTP/Request2/Adapter/Mock.php b/lib/ext/HTTP/Request2/Adapter/Mock.php
deleted file mode 100644
index 6e9f827..0000000
--- a/lib/ext/HTTP/Request2/Adapter/Mock.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?php
-/**
- * Mock adapter intended for testing
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Mock.php 308322 2011-02-14 13:58:03Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Base class for HTTP_Request2 adapters
- */
-require_once 'HTTP/Request2/Adapter.php';
-
-/**
- * Mock adapter intended for testing
- *
- * Can be used to test applications depending on HTTP_Request2 package without
- * actually performing any HTTP requests. This adapter will return responses
- * previously added via addResponse()
- * <code>
- * $mock = new HTTP_Request2_Adapter_Mock();
- * $mock->addResponse("HTTP/1.1 ... ");
- *
- * $request = new HTTP_Request2();
- * $request->setAdapter($mock);
- *
- * // This will return the response set above
- * $response = $req->send();
- * </code>
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- */
-class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
-{
- /**
- * A queue of responses to be returned by sendRequest()
- * @var array
- */
- protected $responses = array();
-
- /**
- * Returns the next response from the queue built by addResponse()
- *
- * If the queue is empty it will return default empty response with status 400,
- * if an Exception object was added to the queue it will be thrown.
- *
- * @param HTTP_Request2
- * @return HTTP_Request2_Response
- * @throws Exception
- */
- public function sendRequest(HTTP_Request2 $request)
- {
- if (count($this->responses) > 0) {
- $response = array_shift($this->responses);
- if ($response instanceof HTTP_Request2_Response) {
- return $response;
- } else {
- // rethrow the exception
- $class = get_class($response);
- $message = $response->getMessage();
- $code = $response->getCode();
- throw new $class($message, $code);
- }
- } else {
- return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
- }
- }
-
- /**
- * Adds response to the queue
- *
- * @param mixed either a string, a pointer to an open file,
- * an instance of HTTP_Request2_Response or Exception
- * @throws HTTP_Request2_Exception
- */
- public function addResponse($response)
- {
- if (is_string($response)) {
- $response = self::createResponseFromString($response);
- } elseif (is_resource($response)) {
- $response = self::createResponseFromFile($response);
- } elseif (!$response instanceof HTTP_Request2_Response &&
- !$response instanceof Exception
- ) {
- throw new HTTP_Request2_Exception('Parameter is not a valid response');
- }
- $this->responses[] = $response;
- }
-
- /**
- * Creates a new HTTP_Request2_Response object from a string
- *
- * @param string
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- public static function createResponseFromString($str)
- {
- $parts = preg_split('!(\r?\n){2}!m', $str, 2);
- $headerLines = explode("\n", $parts[0]);
- $response = new HTTP_Request2_Response(array_shift($headerLines));
- foreach ($headerLines as $headerLine) {
- $response->parseHeaderLine($headerLine);
- }
- $response->parseHeaderLine('');
- if (isset($parts[1])) {
- $response->appendBody($parts[1]);
- }
- return $response;
- }
-
- /**
- * Creates a new HTTP_Request2_Response object from a file
- *
- * @param resource file pointer returned by fopen()
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- public static function createResponseFromFile($fp)
- {
- $response = new HTTP_Request2_Response(fgets($fp));
- do {
- $headerLine = fgets($fp);
- $response->parseHeaderLine($headerLine);
- } while ('' != trim($headerLine));
-
- while (!feof($fp)) {
- $response->appendBody(fread($fp, 8192));
- }
- return $response;
- }
-}
-?>
\ No newline at end of file
diff --git a/lib/ext/HTTP/Request2/Adapter/Socket.php b/lib/ext/HTTP/Request2/Adapter/Socket.php
deleted file mode 100644
index 05cac6e..0000000
--- a/lib/ext/HTTP/Request2/Adapter/Socket.php
+++ /dev/null
@@ -1,1084 +0,0 @@
-<?php
-/**
- * Socket-based adapter for HTTP_Request2
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Socket.php 309921 2011-04-03 16:43:02Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Base class for HTTP_Request2 adapters
- */
-require_once 'HTTP/Request2/Adapter.php';
-
-/**
- * Socket-based adapter for HTTP_Request2
- *
- * This adapter uses only PHP sockets and will work on almost any PHP
- * environment. Code is based on original HTTP_Request PEAR package.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- */
-class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
-{
- /**
- * Regular expression for 'token' rule from RFC 2616
- */
- const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
-
- /**
- * Regular expression for 'quoted-string' rule from RFC 2616
- */
- const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"';
-
- /**
- * Connected sockets, needed for Keep-Alive support
- * @var array
- * @see connect()
- */
- protected static $sockets = array();
-
- /**
- * Data for digest authentication scheme
- *
- * The keys for the array are URL prefixes.
- *
- * The values are associative arrays with data (realm, nonce, nonce-count,
- * opaque...) needed for digest authentication. Stored here to prevent making
- * duplicate requests to digest-protected resources after we have already
- * received the challenge.
- *
- * @var array
- */
- protected static $challenges = array();
-
- /**
- * Connected socket
- * @var resource
- * @see connect()
- */
- protected $socket;
-
- /**
- * Challenge used for server digest authentication
- * @var array
- */
- protected $serverChallenge;
-
- /**
- * Challenge used for proxy digest authentication
- * @var array
- */
- protected $proxyChallenge;
-
- /**
- * Sum of start time and global timeout, exception will be thrown if request continues past this time
- * @var integer
- */
- protected $deadline = null;
-
- /**
- * Remaining length of the current chunk, when reading chunked response
- * @var integer
- * @see readChunked()
- */
- protected $chunkLength = 0;
-
- /**
- * Remaining amount of redirections to follow
- *
- * Starts at 'max_redirects' configuration parameter and is reduced on each
- * subsequent redirect. An Exception will be thrown once it reaches zero.
- *
- * @var integer
- */
- protected $redirectCountdown = null;
-
- /**
- * Sends request to the remote server and returns its response
- *
- * @param HTTP_Request2
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- public function sendRequest(HTTP_Request2 $request)
- {
- $this->request = $request;
-
- // Use global request timeout if given, see feature requests #5735, #8964
- if ($timeout = $request->getConfig('timeout')) {
- $this->deadline = time() + $timeout;
- } else {
- $this->deadline = null;
- }
-
- try {
- $keepAlive = $this->connect();
- $headers = $this->prepareHeaders();
- if (false === @fwrite($this->socket, $headers, strlen($headers))) {
- throw new HTTP_Request2_MessageException('Error writing request');
- }
- // provide request headers to the observer, see request #7633
- $this->request->setLastEvent('sentHeaders', $headers);
- $this->writeBody();
-
- if ($this->deadline && time() > $this->deadline) {
- throw new HTTP_Request2_MessageException(
- 'Request timed out after ' .
- $request->getConfig('timeout') . ' second(s)',
- HTTP_Request2_Exception::TIMEOUT
- );
- }
-
- $response = $this->readResponse();
-
- if ($jar = $request->getCookieJar()) {
- $jar->addCookiesFromResponse($response, $request->getUrl());
- }
-
- if (!$this->canKeepAlive($keepAlive, $response)) {
- $this->disconnect();
- }
-
- if ($this->shouldUseProxyDigestAuth($response)) {
- return $this->sendRequest($request);
- }
- if ($this->shouldUseServerDigestAuth($response)) {
- return $this->sendRequest($request);
- }
- if ($authInfo = $response->getHeader('authentication-info')) {
- $this->updateChallenge($this->serverChallenge, $authInfo);
- }
- if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {
- $this->updateChallenge($this->proxyChallenge, $proxyInfo);
- }
-
- } catch (Exception $e) {
- $this->disconnect();
- }
-
- unset($this->request, $this->requestBody);
-
- if (!empty($e)) {
- $this->redirectCountdown = null;
- throw $e;
- }
-
- if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) {
- $this->redirectCountdown = null;
- return $response;
- } else {
- return $this->handleRedirect($request, $response);
- }
- }
-
- /**
- * Connects to the remote server
- *
- * @return bool whether the connection can be persistent
- * @throws HTTP_Request2_Exception
- */
- protected function connect()
- {
- $secure = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');
- $tunnel = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
- $headers = $this->request->getHeaders();
- $reqHost = $this->request->getUrl()->getHost();
- if (!($reqPort = $this->request->getUrl()->getPort())) {
- $reqPort = $secure? 443: 80;
- }
-
- if ($host = $this->request->getConfig('proxy_host')) {
- if (!($port = $this->request->getConfig('proxy_port'))) {
- throw new HTTP_Request2_LogicException(
- 'Proxy port not provided',
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- $proxy = true;
- } else {
- $host = $reqHost;
- $port = $reqPort;
- $proxy = false;
- }
-
- if ($tunnel && !$proxy) {
- throw new HTTP_Request2_LogicException(
- "Trying to perform CONNECT request without proxy",
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- if ($secure && !in_array('ssl', stream_get_transports())) {
- throw new HTTP_Request2_LogicException(
- 'Need OpenSSL support for https:// requests',
- HTTP_Request2_Exception::MISCONFIGURATION
- );
- }
-
- // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
- // connection token to a proxy server...
- if ($proxy && !$secure &&
- !empty($headers['connection']) && 'Keep-Alive' == $headers['connection']
- ) {
- $this->request->setHeader('connection');
- }
-
- $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&
- empty($headers['connection'])) ||
- (!empty($headers['connection']) &&
- 'Keep-Alive' == $headers['connection']);
- $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host;
-
- $options = array();
- if ($secure || $tunnel) {
- foreach ($this->request->getConfig() as $name => $value) {
- if ('ssl_' == substr($name, 0, 4) && null !== $value) {
- if ('ssl_verify_host' == $name) {
- if ($value) {
- $options['CN_match'] = $reqHost;
- }
- } else {
- $options[substr($name, 4)] = $value;
- }
- }
- }
- ksort($options);
- }
-
- // Changing SSL context options after connection is established does *not*
- // work, we need a new connection if options change
- $remote = $host . ':' . $port;
- $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') .
- (empty($options)? '': ':' . serialize($options));
- unset($this->socket);
-
- // We use persistent connections and have a connected socket?
- // Ensure that the socket is still connected, see bug #16149
- if ($keepAlive && !empty(self::$sockets[$socketKey]) &&
- !feof(self::$sockets[$socketKey])
- ) {
- $this->socket =& self::$sockets[$socketKey];
-
- } elseif ($secure && $proxy && !$tunnel) {
- $this->establishTunnel();
- $this->request->setLastEvent(
- 'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}"
- );
- self::$sockets[$socketKey] =& $this->socket;
-
- } else {
- // Set SSL context options if doing HTTPS request or creating a tunnel
- $context = stream_context_create();
- foreach ($options as $name => $value) {
- if (!stream_context_set_option($context, 'ssl', $name, $value)) {
- throw new HTTP_Request2_LogicException(
- "Error setting SSL context option '{$name}'"
- );
- }
- }
- $track = @ini_set('track_errors', 1);
- $this->socket = @stream_socket_client(
- $remote, $errno, $errstr,
- $this->request->getConfig('connect_timeout'),
- STREAM_CLIENT_CONNECT, $context
- );
- if (!$this->socket) {
- $e = new HTTP_Request2_ConnectionException(
- "Unable to connect to {$remote}. Error: "
- . (empty($errstr)? $php_errormsg: $errstr), 0, $errno
- );
- }
- @ini_set('track_errors', $track);
- if (isset($e)) {
- throw $e;
- }
- $this->request->setLastEvent('connect', $remote);
- self::$sockets[$socketKey] =& $this->socket;
- }
- return $keepAlive;
- }
-
- /**
- * Establishes a tunnel to a secure remote server via HTTP CONNECT request
- *
- * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP
- * sees that we are connected to a proxy server (duh!) rather than the server
- * that presents its certificate.
- *
- * @link http://tools.ietf.org/html/rfc2817#section-5.2
- * @throws HTTP_Request2_Exception
- */
- protected function establishTunnel()
- {
- $donor = new self;
- $connect = new HTTP_Request2(
- $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,
- array_merge($this->request->getConfig(),
- array('adapter' => $donor))
- );
- $response = $connect->send();
- // Need any successful (2XX) response
- if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
- throw new HTTP_Request2_ConnectionException(
- 'Failed to connect via HTTPS proxy. Proxy response: ' .
- $response->getStatus() . ' ' . $response->getReasonPhrase()
- );
- }
- $this->socket = $donor->socket;
-
- $modes = array(
- STREAM_CRYPTO_METHOD_TLS_CLIENT,
- STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
- STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
- STREAM_CRYPTO_METHOD_SSLv2_CLIENT
- );
-
- foreach ($modes as $mode) {
- if (stream_socket_enable_crypto($this->socket, true, $mode)) {
- return;
- }
- }
- throw new HTTP_Request2_ConnectionException(
- 'Failed to enable secure connection when connecting through proxy'
- );
- }
-
- /**
- * Checks whether current connection may be reused or should be closed
- *
- * @param boolean whether connection could be persistent
- * in the first place
- * @param HTTP_Request2_Response response object to check
- * @return boolean
- */
- protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
- {
- // Do not close socket on successful CONNECT request
- if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
- 200 <= $response->getStatus() && 300 > $response->getStatus()
- ) {
- return true;
- }
-
- $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding'))
- || null !== $response->getHeader('content-length')
- // no body possible for such responses, see also request #17031
- || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
- || in_array($response->getStatus(), array(204, 304));
- $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) ||
- (null === $response->getHeader('connection') &&
- '1.1' == $response->getVersion());
- return $requestKeepAlive && $lengthKnown && $persistent;
- }
-
- /**
- * Disconnects from the remote server
- */
- protected function disconnect()
- {
- if (is_resource($this->socket)) {
- fclose($this->socket);
- $this->socket = null;
- $this->request->setLastEvent('disconnect');
- }
- }
-
- /**
- * Handles HTTP redirection
- *
- * This method will throw an Exception if redirect to a non-HTTP(S) location
- * is attempted, also if number of redirects performed already is equal to
- * 'max_redirects' configuration parameter.
- *
- * @param HTTP_Request2 Original request
- * @param HTTP_Request2_Response Response containing redirect
- * @return HTTP_Request2_Response Response from a new location
- * @throws HTTP_Request2_Exception
- */
- protected function handleRedirect(HTTP_Request2 $request,
- HTTP_Request2_Response $response)
- {
- if (is_null($this->redirectCountdown)) {
- $this->redirectCountdown = $request->getConfig('max_redirects');
- }
- if (0 == $this->redirectCountdown) {
- $this->redirectCountdown = null;
- // Copying cURL behaviour
- throw new HTTP_Request2_MessageException (
- 'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed',
- HTTP_Request2_Exception::TOO_MANY_REDIRECTS
- );
- }
- $redirectUrl = new Net_URL2(
- $response->getHeader('location'),
- array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets'))
- );
- // refuse non-HTTP redirect
- if ($redirectUrl->isAbsolute()
- && !in_array($redirectUrl->getScheme(), array('http', 'https'))
- ) {
- $this->redirectCountdown = null;
- throw new HTTP_Request2_MessageException(
- 'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),
- HTTP_Request2_Exception::NON_HTTP_REDIRECT
- );
- }
- // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30),
- // but in practice it is often not
- if (!$redirectUrl->isAbsolute()) {
- $redirectUrl = $request->getUrl()->resolve($redirectUrl);
- }
- $redirect = clone $request;
- $redirect->setUrl($redirectUrl);
- if (303 == $response->getStatus() || (!$request->getConfig('strict_redirects')
- && in_array($response->getStatus(), array(301, 302)))
- ) {
- $redirect->setMethod(HTTP_Request2::METHOD_GET);
- $redirect->setBody('');
- }
-
- if (0 < $this->redirectCountdown) {
- $this->redirectCountdown--;
- }
- return $this->sendRequest($redirect);
- }
-
- /**
- * Checks whether another request should be performed with server digest auth
- *
- * Several conditions should be satisfied for it to return true:
- * - response status should be 401
- * - auth credentials should be set in the request object
- * - response should contain WWW-Authenticate header with digest challenge
- * - there is either no challenge stored for this URL or new challenge
- * contains stale=true parameter (in other case we probably just failed
- * due to invalid username / password)
- *
- * The method stores challenge values in $challenges static property
- *
- * @param HTTP_Request2_Response response to check
- * @return boolean whether another request should be performed
- * @throws HTTP_Request2_Exception in case of unsupported challenge parameters
- */
- protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)
- {
- // no sense repeating a request if we don't have credentials
- if (401 != $response->getStatus() || !$this->request->getAuth()) {
- return false;
- }
- if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {
- return false;
- }
-
- $url = $this->request->getUrl();
- $scheme = $url->getScheme();
- $host = $scheme . '://' . $url->getHost();
- if ($port = $url->getPort()) {
- if ((0 == strcasecmp($scheme, 'http') && 80 != $port) ||
- (0 == strcasecmp($scheme, 'https') && 443 != $port)
- ) {
- $host .= ':' . $port;
- }
- }
-
- if (!empty($challenge['domain'])) {
- $prefixes = array();
- foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {
- // don't bother with different servers
- if ('/' == substr($prefix, 0, 1)) {
- $prefixes[] = $host . $prefix;
- }
- }
- }
- if (empty($prefixes)) {
- $prefixes = array($host . '/');
- }
-
- $ret = true;
- foreach ($prefixes as $prefix) {
- if (!empty(self::$challenges[$prefix]) &&
- (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
- ) {
- // probably credentials are invalid
- $ret = false;
- }
- self::$challenges[$prefix] =& $challenge;
- }
- return $ret;
- }
-
- /**
- * Checks whether another request should be performed with proxy digest auth
- *
- * Several conditions should be satisfied for it to return true:
- * - response status should be 407
- * - proxy auth credentials should be set in the request object
- * - response should contain Proxy-Authenticate header with digest challenge
- * - there is either no challenge stored for this proxy or new challenge
- * contains stale=true parameter (in other case we probably just failed
- * due to invalid username / password)
- *
- * The method stores challenge values in $challenges static property
- *
- * @param HTTP_Request2_Response response to check
- * @return boolean whether another request should be performed
- * @throws HTTP_Request2_Exception in case of unsupported challenge parameters
- */
- protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
- {
- if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {
- return false;
- }
- if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {
- return false;
- }
-
- $key = 'proxy://' . $this->request->getConfig('proxy_host') .
- ':' . $this->request->getConfig('proxy_port');
-
- if (!empty(self::$challenges[$key]) &&
- (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
- ) {
- $ret = false;
- } else {
- $ret = true;
- }
- self::$challenges[$key] = $challenge;
- return $ret;
- }
-
- /**
- * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value
- *
- * There is a problem with implementation of RFC 2617: several of the parameters
- * are defined as quoted-string there and thus may contain backslash escaped
- * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as
- * just value of quoted-string X without surrounding quotes, it doesn't speak
- * about removing backslash escaping.
- *
- * Now realm parameter is user-defined and human-readable, strange things
- * happen when it contains quotes:
- * - Apache allows quotes in realm, but apparently uses realm value without
- * backslashes for digest computation
- * - Squid allows (manually escaped) quotes there, but it is impossible to
- * authorize with either escaped or unescaped quotes used in digest,
- * probably it can't parse the response (?)
- * - Both IE and Firefox display realm value with backslashes in
- * the password popup and apparently use the same value for digest
- *
- * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in
- * quoted-string handling, unfortunately that means failure to authorize
- * sometimes
- *
- * @param string value of WWW-Authenticate or Proxy-Authenticate header
- * @return mixed associative array with challenge parameters, false if
- * no challenge is present in header value
- * @throws HTTP_Request2_NotImplementedException in case of unsupported challenge parameters
- */
- protected function parseDigestChallenge($headerValue)
- {
- $authParam = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
- self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';
- $challenge = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
- if (!preg_match($challenge, $headerValue, $matches)) {
- return false;
- }
-
- preg_match_all('!' . $authParam . '!', $matches[0], $params);
- $paramsAry = array();
- $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',
- 'algorithm', 'qop');
- for ($i = 0; $i < count($params[0]); $i++) {
- // section 3.2.1: Any unrecognized directive MUST be ignored.
- if (in_array($params[1][$i], $knownParams)) {
- if ('"' == substr($params[2][$i], 0, 1)) {
- $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
- } else {
- $paramsAry[$params[1][$i]] = $params[2][$i];
- }
- }
- }
- // we only support qop=auth
- if (!empty($paramsAry['qop']) &&
- !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))
- ) {
- throw new HTTP_Request2_NotImplementedException(
- "Only 'auth' qop is currently supported in digest authentication, " .
- "server requested '{$paramsAry['qop']}'"
- );
- }
- // we only support algorithm=MD5
- if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {
- throw new HTTP_Request2_NotImplementedException(
- "Only 'MD5' algorithm is currently supported in digest authentication, " .
- "server requested '{$paramsAry['algorithm']}'"
- );
- }
-
- return $paramsAry;
- }
-
- /**
- * Parses [Proxy-]Authentication-Info header value and updates challenge
- *
- * @param array challenge to update
- * @param string value of [Proxy-]Authentication-Info header
- * @todo validate server rspauth response
- */
- protected function updateChallenge(&$challenge, $headerValue)
- {
- $authParam = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
- self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';
- $paramsAry = array();
-
- preg_match_all($authParam, $headerValue, $params);
- for ($i = 0; $i < count($params[0]); $i++) {
- if ('"' == substr($params[2][$i], 0, 1)) {
- $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
- } else {
- $paramsAry[$params[1][$i]] = $params[2][$i];
- }
- }
- // for now, just update the nonce value
- if (!empty($paramsAry['nextnonce'])) {
- $challenge['nonce'] = $paramsAry['nextnonce'];
- $challenge['nc'] = 1;
- }
- }
-
- /**
- * Creates a value for [Proxy-]Authorization header when using digest authentication
- *
- * @param string user name
- * @param string password
- * @param string request URL
- * @param array digest challenge parameters
- * @return string value of [Proxy-]Authorization request header
- * @link http://tools.ietf.org/html/rfc2617#section-3.2.2
- */
- protected function createDigestResponse($user, $password, $url, &$challenge)
- {
- if (false !== ($q = strpos($url, '?')) &&
- $this->request->getConfig('digest_compat_ie')
- ) {
- $url = substr($url, 0, $q);
- }
-
- $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);
- $a2 = md5($this->request->getMethod() . ':' . $url);
-
- if (empty($challenge['qop'])) {
- $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
- } else {
- $challenge['cnonce'] = 'Req2.' . rand();
- if (empty($challenge['nc'])) {
- $challenge['nc'] = 1;
- }
- $nc = sprintf('%08x', $challenge['nc']++);
- $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .
- $challenge['cnonce'] . ':auth:' . $a2);
- }
- return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .
- 'realm="' . $challenge['realm'] . '", ' .
- 'nonce="' . $challenge['nonce'] . '", ' .
- 'uri="' . $url . '", ' .
- 'response="' . $digest . '"' .
- (!empty($challenge['opaque'])?
- ', opaque="' . $challenge['opaque'] . '"':
- '') .
- (!empty($challenge['qop'])?
- ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
- '');
- }
-
- /**
- * Adds 'Authorization' header (if needed) to request headers array
- *
- * @param array request headers
- * @param string request host (needed for digest authentication)
- * @param string request URL (needed for digest authentication)
- * @throws HTTP_Request2_NotImplementedException
- */
- protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
- {
- if (!($auth = $this->request->getAuth())) {
- return;
- }
- switch ($auth['scheme']) {
- case HTTP_Request2::AUTH_BASIC:
- $headers['authorization'] =
- 'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']);
- break;
-
- case HTTP_Request2::AUTH_DIGEST:
- unset($this->serverChallenge);
- $fullUrl = ('/' == $requestUrl[0])?
- $this->request->getUrl()->getScheme() . '://' .
- $requestHost . $requestUrl:
- $requestUrl;
- foreach (array_keys(self::$challenges) as $key) {
- if ($key == substr($fullUrl, 0, strlen($key))) {
- $headers['authorization'] = $this->createDigestResponse(
- $auth['user'], $auth['password'],
- $requestUrl, self::$challenges[$key]
- );
- $this->serverChallenge =& self::$challenges[$key];
- break;
- }
- }
- break;
-
- default:
- throw new HTTP_Request2_NotImplementedException(
- "Unknown HTTP authentication scheme '{$auth['scheme']}'"
- );
- }
- }
-
- /**
- * Adds 'Proxy-Authorization' header (if needed) to request headers array
- *
- * @param array request headers
- * @param string request URL (needed for digest authentication)
- * @throws HTTP_Request2_NotImplementedException
- */
- protected function addProxyAuthorizationHeader(&$headers, $requestUrl)
- {
- if (!$this->request->getConfig('proxy_host') ||
- !($user = $this->request->getConfig('proxy_user')) ||
- (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) &&
- HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())
- ) {
- return;
- }
-
- $password = $this->request->getConfig('proxy_password');
- switch ($this->request->getConfig('proxy_auth_scheme')) {
- case HTTP_Request2::AUTH_BASIC:
- $headers['proxy-authorization'] =
- 'Basic ' . base64_encode($user . ':' . $password);
- break;
-
- case HTTP_Request2::AUTH_DIGEST:
- unset($this->proxyChallenge);
- $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
- ':' . $this->request->getConfig('proxy_port');
- if (!empty(self::$challenges[$proxyUrl])) {
- $headers['proxy-authorization'] = $this->createDigestResponse(
- $user, $password,
- $requestUrl, self::$challenges[$proxyUrl]
- );
- $this->proxyChallenge =& self::$challenges[$proxyUrl];
- }
- break;
-
- default:
- throw new HTTP_Request2_NotImplementedException(
- "Unknown HTTP authentication scheme '" .
- $this->request->getConfig('proxy_auth_scheme') . "'"
- );
- }
- }
-
-
- /**
- * Creates the string with the Request-Line and request headers
- *
- * @return string
- * @throws HTTP_Request2_Exception
- */
- protected function prepareHeaders()
- {
- $headers = $this->request->getHeaders();
- $url = $this->request->getUrl();
- $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
- $host = $url->getHost();
-
- $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
- if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
- $host .= ':' . (empty($port)? $defaultPort: $port);
- }
- // Do not overwrite explicitly set 'Host' header, see bug #16146
- if (!isset($headers['host'])) {
- $headers['host'] = $host;
- }
-
- if ($connect) {
- $requestUrl = $host;
-
- } else {
- if (!$this->request->getConfig('proxy_host') ||
- 0 == strcasecmp($url->getScheme(), 'https')
- ) {
- $requestUrl = '';
- } else {
- $requestUrl = $url->getScheme() . '://' . $host;
- }
- $path = $url->getPath();
- $query = $url->getQuery();
- $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
- }
-
- if ('1.1' == $this->request->getConfig('protocol_version') &&
- extension_loaded('zlib') && !isset($headers['accept-encoding'])
- ) {
- $headers['accept-encoding'] = 'gzip, deflate';
- }
- if (($jar = $this->request->getCookieJar())
- && ($cookies = $jar->getMatching($this->request->getUrl(), true))
- ) {
- $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
- }
-
- $this->addAuthorizationHeader($headers, $host, $requestUrl);
- $this->addProxyAuthorizationHeader($headers, $requestUrl);
- $this->calculateRequestLength($headers);
-
- $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
- $this->request->getConfig('protocol_version') . "\r\n";
- foreach ($headers as $name => $value) {
- $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
- $headersStr .= $canonicalName . ': ' . $value . "\r\n";
- }
- return $headersStr . "\r\n";
- }
-
- /**
- * Sends the request body
- *
- * @throws HTTP_Request2_MessageException
- */
- protected function writeBody()
- {
- if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
- 0 == $this->contentLength
- ) {
- return;
- }
-
- $position = 0;
- $bufferSize = $this->request->getConfig('buffer_size');
- while ($position < $this->contentLength) {
- if (is_string($this->requestBody)) {
- $str = substr($this->requestBody, $position, $bufferSize);
- } elseif (is_resource($this->requestBody)) {
- $str = fread($this->requestBody, $bufferSize);
- } else {
- $str = $this->requestBody->read($bufferSize);
- }
- if (false === @fwrite($this->socket, $str, strlen($str))) {
- throw new HTTP_Request2_MessageException('Error writing request');
- }
- // Provide the length of written string to the observer, request #7630
- $this->request->setLastEvent('sentBodyPart', strlen($str));
- $position += strlen($str);
- }
- $this->request->setLastEvent('sentBody', $this->contentLength);
- }
-
- /**
- * Reads the remote server's response
- *
- * @return HTTP_Request2_Response
- * @throws HTTP_Request2_Exception
- */
- protected function readResponse()
- {
- $bufferSize = $this->request->getConfig('buffer_size');
-
- do {
- $response = new HTTP_Request2_Response(
- $this->readLine($bufferSize), true, $this->request->getUrl()
- );
- do {
- $headerLine = $this->readLine($bufferSize);
- $response->parseHeaderLine($headerLine);
- } while ('' != $headerLine);
- } while (in_array($response->getStatus(), array(100, 101)));
-
- $this->request->setLastEvent('receivedHeaders', $response);
-
- // No body possible in such responses
- if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() ||
- (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
- 200 <= $response->getStatus() && 300 > $response->getStatus()) ||
- in_array($response->getStatus(), array(204, 304))
- ) {
- return $response;
- }
-
- $chunked = 'chunked' == $response->getHeader('transfer-encoding');
- $length = $response->getHeader('content-length');
- $hasBody = false;
- if ($chunked || null === $length || 0 < intval($length)) {
- // RFC 2616, section 4.4:
- // 3. ... If a message is received with both a
- // Transfer-Encoding header field and a Content-Length header field,
- // the latter MUST be ignored.
- $toRead = ($chunked || null === $length)? null: $length;
- $this->chunkLength = 0;
-
- while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) {
- if ($chunked) {
- $data = $this->readChunked($bufferSize);
- } elseif (is_null($toRead)) {
- $data = $this->fread($bufferSize);
- } else {
- $data = $this->fread(min($toRead, $bufferSize));
- $toRead -= strlen($data);
- }
- if ('' == $data && (!$this->chunkLength || feof($this->socket))) {
- break;
- }
-
- $hasBody = true;
- if ($this->request->getConfig('store_body')) {
- $response->appendBody($data);
- }
- if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
- $this->request->setLastEvent('receivedEncodedBodyPart', $data);
- } else {
- $this->request->setLastEvent('receivedBodyPart', $data);
- }
- }
- }
-
- if ($hasBody) {
- $this->request->setLastEvent('receivedBody', $response);
- }
- return $response;
- }
-
- /**
- * Reads until either the end of the socket or a newline, whichever comes first
- *
- * Strips the trailing newline from the returned data, handles global
- * request timeout. Method idea borrowed from Net_Socket PEAR package.
- *
- * @param int buffer size to use for reading
- * @return Available data up to the newline (not including newline)
- * @throws HTTP_Request2_MessageException In case of timeout
- */
- protected function readLine($bufferSize)
- {
- $line = '';
- while (!feof($this->socket)) {
- if ($this->deadline) {
- stream_set_timeout($this->socket, max($this->deadline - time(), 1));
- }
- $line .= @fgets($this->socket, $bufferSize);
- $info = stream_get_meta_data($this->socket);
- if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {
- $reason = $this->deadline
- ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'
- : 'due to default_socket_timeout php.ini setting';
- throw new HTTP_Request2_MessageException(
- "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT
- );
- }
- if (substr($line, -1) == "\n") {
- return rtrim($line, "\r\n");
- }
- }
- return $line;
- }
-
- /**
- * Wrapper around fread(), handles global request timeout
- *
- * @param int Reads up to this number of bytes
- * @return Data read from socket
- * @throws HTTP_Request2_MessageException In case of timeout
- */
- protected function fread($length)
- {
- if ($this->deadline) {
- stream_set_timeout($this->socket, max($this->deadline - time(), 1));
- }
- $data = fread($this->socket, $length);
- $info = stream_get_meta_data($this->socket);
- if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {
- $reason = $this->deadline
- ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'
- : 'due to default_socket_timeout php.ini setting';
- throw new HTTP_Request2_MessageException(
- "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT
- );
- }
- return $data;
- }
-
- /**
- * Reads a part of response body encoded with chunked Transfer-Encoding
- *
- * @param int buffer size to use for reading
- * @return string
- * @throws HTTP_Request2_MessageException
- */
- protected function readChunked($bufferSize)
- {
- // at start of the next chunk?
- if (0 == $this->chunkLength) {
- $line = $this->readLine($bufferSize);
- if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
- throw new HTTP_Request2_MessageException(
- "Cannot decode chunked response, invalid chunk length '{$line}'",
- HTTP_Request2_Exception::DECODE_ERROR
- );
- } else {
- $this->chunkLength = hexdec($matches[1]);
- // Chunk with zero length indicates the end
- if (0 == $this->chunkLength) {
- $this->readLine($bufferSize);
- return '';
- }
- }
- }
- $data = $this->fread(min($this->chunkLength, $bufferSize));
- $this->chunkLength -= strlen($data);
- if (0 == $this->chunkLength) {
- $this->readLine($bufferSize); // Trailing CRLF
- }
- return $data;
- }
-}
-
-?>
\ No newline at end of file
diff --git a/lib/ext/HTTP/Request2/CookieJar.php b/lib/ext/HTTP/Request2/CookieJar.php
deleted file mode 100644
index af7534f..0000000
--- a/lib/ext/HTTP/Request2/CookieJar.php
+++ /dev/null
@@ -1,499 +0,0 @@
-<?php
-/**
- * Stores cookies and passes them between HTTP requests
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: CookieJar.php 308629 2011-02-24 17:34:24Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/** Class representing a HTTP request message */
-require_once 'HTTP/Request2.php';
-
-/**
- * Stores cookies and passes them between HTTP requests
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: @package_version@
- */
-class HTTP_Request2_CookieJar implements Serializable
-{
- /**
- * Array of stored cookies
- *
- * The array is indexed by domain, path and cookie name
- * .example.com
- * /
- * some_cookie => cookie data
- * /subdir
- * other_cookie => cookie data
- * .example.org
- * ...
- *
- * @var array
- */
- protected $cookies = array();
-
- /**
- * Whether session cookies should be serialized when serializing the jar
- * @var bool
- */
- protected $serializeSession = false;
-
- /**
- * Whether Public Suffix List should be used for domain matching
- * @var bool
- */
- protected $useList = true;
-
- /**
- * Array with Public Suffix List data
- * @var array
- * @link http://publicsuffix.org/
- */
- protected static $psl = array();
-
- /**
- * Class constructor, sets various options
- *
- * @param bool Controls serializing session cookies, see {@link serializeSessionCookies()}
- * @param bool Controls using Public Suffix List, see {@link usePublicSuffixList()}
- */
- public function __construct($serializeSessionCookies = false, $usePublicSuffixList = true)
- {
- $this->serializeSessionCookies($serializeSessionCookies);
- $this->usePublicSuffixList($usePublicSuffixList);
- }
-
- /**
- * Returns current time formatted in ISO-8601 at UTC timezone
- *
- * @return string
- */
- protected function now()
- {
- $dt = new DateTime();
- $dt->setTimezone(new DateTimeZone('UTC'));
- return $dt->format(DateTime::ISO8601);
- }
-
- /**
- * Checks cookie array for correctness, possibly updating its 'domain', 'path' and 'expires' fields
- *
- * The checks are as follows:
- * - cookie array should contain 'name' and 'value' fields;
- * - name and value should not contain disallowed symbols;
- * - 'expires' should be either empty parseable by DateTime;
- * - 'domain' and 'path' should be either not empty or an URL where
- * cookie was set should be provided.
- * - if $setter is provided, then document at that URL should be allowed
- * to set a cookie for that 'domain'. If $setter is not provided,
- * then no domain checks will be made.
- *
- * 'expires' field will be converted to ISO8601 format from COOKIE format,
- * 'domain' and 'path' will be set from setter URL if empty.
- *
- * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()}
- * @param Net_URL2 URL of the document that sent Set-Cookie header
- * @return array Updated cookie array
- * @throws HTTP_Request2_LogicException
- * @throws HTTP_Request2_MessageException
- */
- protected function checkAndUpdateFields(array $cookie, Net_URL2 $setter = null)
- {
- if ($missing = array_diff(array('name', 'value'), array_keys($cookie))) {
- throw new HTTP_Request2_LogicException(
- "Cookie array should contain 'name' and 'value' fields",
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['name'])) {
- throw new HTTP_Request2_LogicException(
- "Invalid cookie name: '{$cookie['name']}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['value'])) {
- throw new HTTP_Request2_LogicException(
- "Invalid cookie value: '{$cookie['value']}'",
- HTTP_Request2_Exception::INVALID_ARGUMENT
- );
- }
- $cookie += array('domain' => '', 'path' => '', 'expires' => null, 'secure' => false);
-
- // Need ISO-8601 date @ UTC timezone
- if (!empty($cookie['expires'])
- && !preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+0000$/', $cookie['expires'])
- ) {
- try {
- $dt = new DateTime($cookie['expires']);
- $dt->setTimezone(new DateTimeZone('UTC'));
- $cookie['expires'] = $dt->format(DateTime::ISO8601);
- } catch (Exception $e) {
- throw new HTTP_Request2_LogicException($e->getMessage());
- }
- }
-
- if (empty($cookie['domain']) || empty($cookie['path'])) {
- if (!$setter) {
- throw new HTTP_Request2_LogicException(
- 'Cookie misses domain and/or path component, cookie setter URL needed',
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- if (empty($cookie['domain'])) {
- if ($host = $setter->getHost()) {
- $cookie['domain'] = $host;
- } else {
- throw new HTTP_Request2_LogicException(
- 'Setter URL does not contain host part, can\'t set cookie domain',
- HTTP_Request2_Exception::MISSING_VALUE
- );
- }
- }
- if (empty($cookie['path'])) {
- $path = $setter->getPath();
- $cookie['path'] = empty($path)? '/': substr($path, 0, strrpos($path, '/') + 1);
- }
- }
-
- if ($setter && !$this->domainMatch($setter->getHost(), $cookie['domain'])) {
- throw new HTTP_Request2_MessageException(
- "Domain " . $setter->getHost() . " cannot set cookies for "
- . $cookie['domain']
- );
- }
-
- return $cookie;
- }
-
- /**
- * Stores a cookie in the jar
- *
- * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()}
- * @param Net_URL2 URL of the document that sent Set-Cookie header
- * @throws HTTP_Request2_Exception
- */
- public function store(array $cookie, Net_URL2 $setter = null)
- {
- $cookie = $this->checkAndUpdateFields($cookie, $setter);
-
- if (strlen($cookie['value'])
- && (is_null($cookie['expires']) || $cookie['expires'] > $this->now())
- ) {
- if (!isset($this->cookies[$cookie['domain']])) {
- $this->cookies[$cookie['domain']] = array();
- }
- if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) {
- $this->cookies[$cookie['domain']][$cookie['path']] = array();
- }
- $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie;
-
- } elseif (isset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']])) {
- unset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']]);
- }
- }
-
- /**
- * Adds cookies set in HTTP response to the jar
- *
- * @param HTTP_Request2_Response response
- * @param Net_URL2 original request URL, needed for setting
- * default domain/path
- */
- public function addCookiesFromResponse(HTTP_Request2_Response $response, Net_URL2 $setter)
- {
- foreach ($response->getCookies() as $cookie) {
- $this->store($cookie, $setter);
- }
- }
-
- /**
- * Returns all cookies matching a given request URL
- *
- * The following checks are made:
- * - cookie domain should match request host
- * - cookie path should be a prefix for request path
- * - 'secure' cookies will only be sent for HTTPS requests
- *
- * @param Net_URL2
- * @param bool Whether to return cookies as string for "Cookie: " header
- * @return array
- */
- public function getMatching(Net_URL2 $url, $asString = false)
- {
- $host = $url->getHost();
- $path = $url->getPath();
- $secure = 0 == strcasecmp($url->getScheme(), 'https');
-
- $matched = $ret = array();
- foreach (array_keys($this->cookies) as $domain) {
- if ($this->domainMatch($host, $domain)) {
- foreach (array_keys($this->cookies[$domain]) as $cPath) {
- if (0 === strpos($path, $cPath)) {
- foreach ($this->cookies[$domain][$cPath] as $name => $cookie) {
- if (!$cookie['secure'] || $secure) {
- $matched[$name][strlen($cookie['path'])] = $cookie;
- }
- }
- }
- }
- }
- }
- foreach ($matched as $cookies) {
- krsort($cookies);
- $ret = array_merge($ret, $cookies);
- }
- if (!$asString) {
- return $ret;
- } else {
- $str = '';
- foreach ($ret as $c) {
- $str .= (empty($str)? '': '; ') . $c['name'] . '=' . $c['value'];
- }
- return $str;
- }
- }
-
- /**
- * Returns all cookies stored in a jar
- *
- * @return array
- */
- public function getAll()
- {
- $cookies = array();
- foreach (array_keys($this->cookies) as $domain) {
- foreach (array_keys($this->cookies[$domain]) as $path) {
- foreach ($this->cookies[$domain][$path] as $name => $cookie) {
- $cookies[] = $cookie;
- }
- }
- }
- return $cookies;
- }
-
- /**
- * Sets whether session cookies should be serialized when serializing the jar
- *
- * @param boolean
- */
- public function serializeSessionCookies($serialize)
- {
- $this->serializeSession = (bool)$serialize;
- }
-
- /**
- * Sets whether Public Suffix List should be used for restricting cookie-setting
- *
- * Without PSL {@link domainMatch()} will only prevent setting cookies for
- * top-level domains like '.com' or '.org'. However, it will not prevent
- * setting a cookie for '.co.uk' even though only third-level registrations
- * are possible in .uk domain.
- *
- * With the List it is possible to find the highest level at which a domain
- * may be registered for a particular top-level domain and consequently
- * prevent cookies set for '.co.uk' or '.msk.ru'. The same list is used by
- * Firefox, Chrome and Opera browsers to restrict cookie setting.
- *
- * Note that PSL is licensed differently to HTTP_Request2 package (refer to
- * the license information in public-suffix-list.php), so you can disable
- * its use if this is an issue for you.
- *
- * @param boolean
- * @link http://publicsuffix.org/learn/
- */
- public function usePublicSuffixList($useList)
- {
- $this->useList = (bool)$useList;
- }
-
- /**
- * Returns string representation of object
- *
- * @return string
- * @see Serializable::serialize()
- */
- public function serialize()
- {
- $cookies = $this->getAll();
- if (!$this->serializeSession) {
- for ($i = count($cookies) - 1; $i >= 0; $i--) {
- if (empty($cookies[$i]['expires'])) {
- unset($cookies[$i]);
- }
- }
- }
- return serialize(array(
- 'cookies' => $cookies,
- 'serializeSession' => $this->serializeSession,
- 'useList' => $this->useList
- ));
- }
-
- /**
- * Constructs the object from serialized string
- *
- * @param string string representation
- * @see Serializable::unserialize()
- */
- public function unserialize($serialized)
- {
- $data = unserialize($serialized);
- $now = $this->now();
- $this->serializeSessionCookies($data['serializeSession']);
- $this->usePublicSuffixList($data['useList']);
- foreach ($data['cookies'] as $cookie) {
- if (!empty($cookie['expires']) && $cookie['expires'] <= $now) {
- continue;
- }
- if (!isset($this->cookies[$cookie['domain']])) {
- $this->cookies[$cookie['domain']] = array();
- }
- if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) {
- $this->cookies[$cookie['domain']][$cookie['path']] = array();
- }
- $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie;
- }
- }
-
- /**
- * Checks whether a cookie domain matches a request host.
- *
- * The method is used by {@link store()} to check for whether a document
- * at given URL can set a cookie with a given domain attribute and by
- * {@link getMatching()} to find cookies matching the request URL.
- *
- * @param string request host
- * @param string cookie domain
- * @return bool match success
- */
- public function domainMatch($requestHost, $cookieDomain)
- {
- if ($requestHost == $cookieDomain) {
- return true;
- }
- // IP address, we require exact match
- if (preg_match('/^(?:\d{1,3}\.){3}\d{1,3}$/', $requestHost)) {
- return false;
- }
- if ('.' != $cookieDomain[0]) {
- $cookieDomain = '.' . $cookieDomain;
- }
- // prevents setting cookies for '.com' and similar domains
- if (!$this->useList && substr_count($cookieDomain, '.') < 2
- || $this->useList && !self::getRegisteredDomain($cookieDomain)
- ) {
- return false;
- }
- return substr('.' . $requestHost, -strlen($cookieDomain)) == $cookieDomain;
- }
-
- /**
- * Removes subdomains to get the registered domain (the first after top-level)
- *
- * The method will check Public Suffix List to find out where top-level
- * domain ends and registered domain starts. It will remove domain parts
- * to the left of registered one.
- *
- * @param string domain name
- * @return string|bool registered domain, will return false if $domain is
- * either invalid or a TLD itself
- */
- public static function getRegisteredDomain($domain)
- {
- $domainParts = explode('.', ltrim($domain, '.'));
-
- // load the list if needed
- if (empty(self::$psl)) {
- $path = '@data_dir@' . DIRECTORY_SEPARATOR . 'HTTP_Request2';
- if (0 === strpos($path, '@' . 'data_dir@')) {
- $path = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'
- . DIRECTORY_SEPARATOR . 'data');
- }
- self::$psl = include_once $path . DIRECTORY_SEPARATOR . 'public-suffix-list.php';
- }
-
- if (!($result = self::checkDomainsList($domainParts, self::$psl))) {
- // known TLD, invalid domain name
- return false;
- }
-
- // unknown TLD
- if (!strpos($result, '.')) {
- // fallback to checking that domain "has at least two dots"
- if (2 > ($count = count($domainParts))) {
- return false;
- }
- return $domainParts[$count - 2] . '.' . $domainParts[$count - 1];
- }
- return $result;
- }
-
- /**
- * Recursive helper method for {@link getRegisteredDomain()}
- *
- * @param array remaining domain parts
- * @param mixed node in {@link HTTP_Request2_CookieJar::$psl} to check
- * @return string|null concatenated domain parts, null in case of error
- */
- protected static function checkDomainsList(array $domainParts, $listNode)
- {
- $sub = array_pop($domainParts);
- $result = null;
-
- if (!is_array($listNode) || is_null($sub)
- || array_key_exists('!' . $sub, $listNode)
- ) {
- return $sub;
-
- } elseif (array_key_exists($sub, $listNode)) {
- $result = self::checkDomainsList($domainParts, $listNode[$sub]);
-
- } elseif (array_key_exists('*', $listNode)) {
- $result = self::checkDomainsList($domainParts, $listNode['*']);
-
- } else {
- return $sub;
- }
-
- return (strlen($result) > 0) ? ($result . '.' . $sub) : null;
- }
-}
-?>
\ No newline at end of file
diff --git a/lib/ext/HTTP/Request2/Exception.php b/lib/ext/HTTP/Request2/Exception.php
deleted file mode 100644
index 34256c2..0000000
--- a/lib/ext/HTTP/Request2/Exception.php
+++ /dev/null
@@ -1,160 +0,0 @@
-<?php
-/**
- * Exception classes for HTTP_Request2 package
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Exception.php 308629 2011-02-24 17:34:24Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Base class for exceptions in PEAR
- */
-require_once 'PEAR/Exception.php';
-
-/**
- * Base exception class for HTTP_Request2 package
- *
- * @category HTTP
- * @package HTTP_Request2
- * @version Release: 2.0.0
- * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=132
- */
-class HTTP_Request2_Exception extends PEAR_Exception
-{
- /** An invalid argument was passed to a method */
- const INVALID_ARGUMENT = 1;
- /** Some required value was not available */
- const MISSING_VALUE = 2;
- /** Request cannot be processed due to errors in PHP configuration */
- const MISCONFIGURATION = 3;
- /** Error reading the local file */
- const READ_ERROR = 4;
-
- /** Server returned a response that does not conform to HTTP protocol */
- const MALFORMED_RESPONSE = 10;
- /** Failure decoding Content-Encoding or Transfer-Encoding of response */
- const DECODE_ERROR = 20;
- /** Operation timed out */
- const TIMEOUT = 30;
- /** Number of redirects exceeded 'max_redirects' configuration parameter */
- const TOO_MANY_REDIRECTS = 40;
- /** Redirect to a protocol other than http(s):// */
- const NON_HTTP_REDIRECT = 50;
-
- /**
- * Native error code
- * @var int
- */
- private $_nativeCode;
-
- /**
- * Constructor, can set package error code and native error code
- *
- * @param string exception message
- * @param int package error code, one of class constants
- * @param int error code from underlying PHP extension
- */
- public function __construct($message = null, $code = null, $nativeCode = null)
- {
- parent::__construct($message, $code);
- $this->_nativeCode = $nativeCode;
- }
-
- /**
- * Returns error code produced by underlying PHP extension
- *
- * For Socket Adapter this may contain error number returned by
- * stream_socket_client(), for Curl Adapter this will contain error number
- * returned by curl_errno()
- *
- * @return integer
- */
- public function getNativeCode()
- {
- return $this->_nativeCode;
- }
-}
-
-/**
- * Exception thrown in case of missing features
- *
- * @category HTTP
- * @package HTTP_Request2
- * @version Release: 2.0.0
- */
-class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception {}
-
-/**
- * Exception that represents error in the program logic
- *
- * This exception usually implies a programmer's error, like passing invalid
- * data to methods or trying to use PHP extensions that weren't installed or
- * enabled. Usually exceptions of this kind will be thrown before request even
- * starts.
- *
- * The exception will usually contain a package error code.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @version Release: 2.0.0
- */
-class HTTP_Request2_LogicException extends HTTP_Request2_Exception {}
-
-/**
- * Exception thrown when connection to a web or proxy server fails
- *
- * The exception will not contain a package error code, but will contain
- * native error code, as returned by stream_socket_client() or curl_errno().
- *
- * @category HTTP
- * @package HTTP_Request2
- * @version Release: 2.0.0
- */
-class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception {}
-
-/**
- * Exception thrown when sending or receiving HTTP message fails
- *
- * The exception may contain both package error code and native error code.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @version Release: 2.0.0
- */
-class HTTP_Request2_MessageException extends HTTP_Request2_Exception {}
-?>
\ No newline at end of file
diff --git a/lib/ext/HTTP/Request2/MultipartBody.php b/lib/ext/HTTP/Request2/MultipartBody.php
deleted file mode 100644
index 021a199..0000000
--- a/lib/ext/HTTP/Request2/MultipartBody.php
+++ /dev/null
@@ -1,274 +0,0 @@
-<?php
-/**
- * Helper class for building multipart/form-data request body
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: MultipartBody.php 308322 2011-02-14 13:58:03Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Class for building multipart/form-data request body
- *
- * The class helps to reduce memory consumption by streaming large file uploads
- * from disk, it also allows monitoring of upload progress (see request #7630)
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- * @link http://tools.ietf.org/html/rfc1867
- */
-class HTTP_Request2_MultipartBody
-{
- /**
- * MIME boundary
- * @var string
- */
- private $_boundary;
-
- /**
- * Form parameters added via {@link HTTP_Request2::addPostParameter()}
- * @var array
- */
- private $_params = array();
-
- /**
- * File uploads added via {@link HTTP_Request2::addUpload()}
- * @var array
- */
- private $_uploads = array();
-
- /**
- * Header for parts with parameters
- * @var string
- */
- private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";
-
- /**
- * Header for parts with uploads
- * @var string
- */
- private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
-
- /**
- * Current position in parameter and upload arrays
- *
- * First number is index of "current" part, second number is position within
- * "current" part
- *
- * @var array
- */
- private $_pos = array(0, 0);
-
-
- /**
- * Constructor. Sets the arrays with POST data.
- *
- * @param array values of form fields set via {@link HTTP_Request2::addPostParameter()}
- * @param array file uploads set via {@link HTTP_Request2::addUpload()}
- * @param bool whether to append brackets to array variable names
- */
- public function __construct(array $params, array $uploads, $useBrackets = true)
- {
- $this->_params = self::_flattenArray('', $params, $useBrackets);
- foreach ($uploads as $fieldName => $f) {
- if (!is_array($f['fp'])) {
- $this->_uploads[] = $f + array('name' => $fieldName);
- } else {
- for ($i = 0; $i < count($f['fp']); $i++) {
- $upload = array(
- 'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)
- );
- foreach (array('fp', 'filename', 'size', 'type') as $key) {
- $upload[$key] = $f[$key][$i];
- }
- $this->_uploads[] = $upload;
- }
- }
- }
- }
-
- /**
- * Returns the length of the body to use in Content-Length header
- *
- * @return integer
- */
- public function getLength()
- {
- $boundaryLength = strlen($this->getBoundary());
- $headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength;
- $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;
- $length = $boundaryLength + 6;
- foreach ($this->_params as $p) {
- $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;
- }
- foreach ($this->_uploads as $u) {
- $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +
- strlen($u['filename']) + $u['size'] + 2;
- }
- return $length;
- }
-
- /**
- * Returns the boundary to use in Content-Type header
- *
- * @return string
- */
- public function getBoundary()
- {
- if (empty($this->_boundary)) {
- $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());
- }
- return $this->_boundary;
- }
-
- /**
- * Returns next chunk of request body
- *
- * @param integer Amount of bytes to read
- * @return string Up to $length bytes of data, empty string if at end
- */
- public function read($length)
- {
- $ret = '';
- $boundary = $this->getBoundary();
- $paramCount = count($this->_params);
- $uploadCount = count($this->_uploads);
- while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {
- $oldLength = $length;
- if ($this->_pos[0] < $paramCount) {
- $param = sprintf($this->_headerParam, $boundary,
- $this->_params[$this->_pos[0]][0]) .
- $this->_params[$this->_pos[0]][1] . "\r\n";
- $ret .= substr($param, $this->_pos[1], $length);
- $length -= min(strlen($param) - $this->_pos[1], $length);
-
- } elseif ($this->_pos[0] < $paramCount + $uploadCount) {
- $pos = $this->_pos[0] - $paramCount;
- $header = sprintf($this->_headerUpload, $boundary,
- $this->_uploads[$pos]['name'],
- $this->_uploads[$pos]['filename'],
- $this->_uploads[$pos]['type']);
- if ($this->_pos[1] < strlen($header)) {
- $ret .= substr($header, $this->_pos[1], $length);
- $length -= min(strlen($header) - $this->_pos[1], $length);
- }
- $filePos = max(0, $this->_pos[1] - strlen($header));
- if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) {
- $ret .= fread($this->_uploads[$pos]['fp'], $length);
- $length -= min($length, $this->_uploads[$pos]['size'] - $filePos);
- }
- if ($length > 0) {
- $start = $this->_pos[1] + ($oldLength - $length) -
- strlen($header) - $this->_uploads[$pos]['size'];
- $ret .= substr("\r\n", $start, $length);
- $length -= min(2 - $start, $length);
- }
-
- } else {
- $closing = '--' . $boundary . "--\r\n";
- $ret .= substr($closing, $this->_pos[1], $length);
- $length -= min(strlen($closing) - $this->_pos[1], $length);
- }
- if ($length > 0) {
- $this->_pos = array($this->_pos[0] + 1, 0);
- } else {
- $this->_pos[1] += $oldLength;
- }
- }
- return $ret;
- }
-
- /**
- * Sets the current position to the start of the body
- *
- * This allows reusing the same body in another request
- */
- public function rewind()
- {
- $this->_pos = array(0, 0);
- foreach ($this->_uploads as $u) {
- rewind($u['fp']);
- }
- }
-
- /**
- * Returns the body as string
- *
- * Note that it reads all file uploads into memory so it is a good idea not
- * to use this method with large file uploads and rely on read() instead.
- *
- * @return string
- */
- public function __toString()
- {
- $this->rewind();
- return $this->read($this->getLength());
- }
-
-
- /**
- * Helper function to change the (probably multidimensional) associative array
- * into the simple one.
- *
- * @param string name for item
- * @param mixed item's values
- * @param bool whether to append [] to array variables' names
- * @return array array with the following items: array('item name', 'item value');
- */
- private static function _flattenArray($name, $values, $useBrackets)
- {
- if (!is_array($values)) {
- return array(array($name, $values));
- } else {
- $ret = array();
- foreach ($values as $k => $v) {
- if (empty($name)) {
- $newName = $k;
- } elseif ($useBrackets) {
- $newName = $name . '[' . $k . ']';
- } else {
- $newName = $name;
- }
- $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));
- }
- return $ret;
- }
- }
-}
-?>
diff --git a/lib/ext/HTTP/Request2/Observer/Log.php b/lib/ext/HTTP/Request2/Observer/Log.php
deleted file mode 100644
index dd59859..0000000
--- a/lib/ext/HTTP/Request2/Observer/Log.php
+++ /dev/null
@@ -1,215 +0,0 @@
-<?php
-/**
- * An observer useful for debugging / testing.
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author David Jean Louis <izi@php.net>
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Log.php 308680 2011-02-25 17:40:17Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Exception class for HTTP_Request2 package
- */
-require_once 'HTTP/Request2/Exception.php';
-
-/**
- * A debug observer useful for debugging / testing.
- *
- * This observer logs to a log target data corresponding to the various request
- * and response events, it logs by default to php://output but can be configured
- * to log to a file or via the PEAR Log package.
- *
- * A simple example:
- * <code>
- * require_once 'HTTP/Request2.php';
- * require_once 'HTTP/Request2/Observer/Log.php';
- *
- * $request = new HTTP_Request2('http://www.example.com');
- * $observer = new HTTP_Request2_Observer_Log();
- * $request->attach($observer);
- * $request->send();
- * </code>
- *
- * A more complex example with PEAR Log:
- * <code>
- * require_once 'HTTP/Request2.php';
- * require_once 'HTTP/Request2/Observer/Log.php';
- * require_once 'Log.php';
- *
- * $request = new HTTP_Request2('http://www.example.com');
- * // we want to log with PEAR log
- * $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));
- *
- * // we only want to log received headers
- * $observer->events = array('receivedHeaders');
- *
- * $request->attach($observer);
- * $request->send();
- * </code>
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author David Jean Louis <izi@php.net>
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version Release: 2.0.0
- * @link http://pear.php.net/package/HTTP_Request2
- */
-class HTTP_Request2_Observer_Log implements SplObserver
-{
- // properties {{{
-
- /**
- * The log target, it can be a a resource or a PEAR Log instance.
- *
- * @var resource|Log $target
- */
- protected $target = null;
-
- /**
- * The events to log.
- *
- * @var array $events
- */
- public $events = array(
- 'connect',
- 'sentHeaders',
- 'sentBody',
- 'receivedHeaders',
- 'receivedBody',
- 'disconnect',
- );
-
- // }}}
- // __construct() {{{
-
- /**
- * Constructor.
- *
- * @param mixed $target Can be a file path (default: php://output), a resource,
- * or an instance of the PEAR Log class.
- * @param array $events Array of events to listen to (default: all events)
- *
- * @return void
- */
- public function __construct($target = 'php://output', array $events = array())
- {
- if (!empty($events)) {
- $this->events = $events;
- }
- if (is_resource($target) || $target instanceof Log) {
- $this->target = $target;
- } elseif (false === ($this->target = @fopen($target, 'ab'))) {
- throw new HTTP_Request2_Exception("Unable to open '{$target}'");
- }
- }
-
- // }}}
- // update() {{{
-
- /**
- * Called when the request notifies us of an event.
- *
- * @param HTTP_Request2 $subject The HTTP_Request2 instance
- *
- * @return void
- */
- public function update(SplSubject $subject)
- {
- $event = $subject->getLastEvent();
- if (!in_array($event['name'], $this->events)) {
- return;
- }
-
- switch ($event['name']) {
- case 'connect':
- $this->log('* Connected to ' . $event['data']);
- break;
- case 'sentHeaders':
- $headers = explode("\r\n", $event['data']);
- array_pop($headers);
- foreach ($headers as $header) {
- $this->log('> ' . $header);
- }
- break;
- case 'sentBody':
- $this->log('> ' . $event['data'] . ' byte(s) sent');
- break;
- case 'receivedHeaders':
- $this->log(sprintf('< HTTP/%s %s %s',
- $event['data']->getVersion(),
- $event['data']->getStatus(),
- $event['data']->getReasonPhrase()));
- $headers = $event['data']->getHeader();
- foreach ($headers as $key => $val) {
- $this->log('< ' . $key . ': ' . $val);
- }
- $this->log('< ');
- break;
- case 'receivedBody':
- $this->log($event['data']->getBody());
- break;
- case 'disconnect':
- $this->log('* Disconnected');
- break;
- }
- }
-
- // }}}
- // log() {{{
-
- /**
- * Logs the given message to the configured target.
- *
- * @param string $message Message to display
- *
- * @return void
- */
- protected function log($message)
- {
- if ($this->target instanceof Log) {
- $this->target->debug($message);
- } elseif (is_resource($this->target)) {
- fwrite($this->target, $message . "\r\n");
- }
- }
-
- // }}}
-}
-
-?>
\ No newline at end of file
diff --git a/lib/ext/HTTP/Request2/Response.php b/lib/ext/HTTP/Request2/Response.php
deleted file mode 100644
index 6e0f659..0000000
--- a/lib/ext/HTTP/Request2/Response.php
+++ /dev/null
@@ -1,643 +0,0 @@
-<?php
-/**
- * Class representing a HTTP response
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * 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.
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version SVN: $Id: Response.php 317591 2011-10-01 08:37:49Z avb $
- * @link http://pear.php.net/package/HTTP_Request2
- */
-
-/**
- * Exception class for HTTP_Request2 package
- */
-require_once 'HTTP/Request2/Exception.php';
-
-/**
- * Class representing a HTTP response
- *
- * The class is designed to be used in "streaming" scenario, building the
- * response as it is being received:
- * <code>
- * $statusLine = read_status_line();
- * $response = new HTTP_Request2_Response($statusLine);
- * do {
- * $headerLine = read_header_line();
- * $response->parseHeaderLine($headerLine);
- * } while ($headerLine != '');
- *
- * while ($chunk = read_body()) {
- * $response->appendBody($chunk);
- * }
- *
- * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());
- * </code>
- *
- *
- * @category HTTP
- * @package HTTP_Request2
- * @author Alexey Borzov <avb@php.net>
- * @version Release: 2.0.0
- * @link http://tools.ietf.org/html/rfc2616#section-6
- */
-class HTTP_Request2_Response
-{
- /**
- * HTTP protocol version (e.g. 1.0, 1.1)
- * @var string
- */
- protected $version;
-
- /**
- * Status code
- * @var integer
- * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
- */
- protected $code;
-
- /**
- * Reason phrase
- * @var string
- * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
- */
- protected $reasonPhrase;
-
- /**
- * Effective URL (may be different from original request URL in case of redirects)
- * @var string
- */
- protected $effectiveUrl;
-
- /**
- * Associative array of response headers
- * @var array
- */
- protected $headers = array();
-
- /**
- * Cookies set in the response
- * @var array
- */
- protected $cookies = array();
-
- /**
- * Name of last header processed by parseHederLine()
- *
- * Used to handle the headers that span multiple lines
- *
- * @var string
- */
- protected $lastHeader = null;
-
- /**
- * Response body
- * @var string
- */
- protected $body = '';
-
- /**
- * Whether the body is still encoded by Content-Encoding
- *
- * cURL provides the decoded body to the callback; if we are reading from
- * socket the body is still gzipped / deflated
- *
- * @var bool
- */
- protected $bodyEncoded;
-
- /**
- * Associative array of HTTP status code / reason phrase.
- *
- * @var array
- * @link http://tools.ietf.org/html/rfc2616#section-10
- */
- protected static $phrases = array(
-
- // 1xx: Informational - Request received, continuing process
- 100 => 'Continue',
- 101 => 'Switching Protocols',
-
- // 2xx: Success - The action was successfully received, understood and
- // accepted
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
-
- // 3xx: Redirection - Further action must be taken in order to complete
- // the request
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found', // 1.1
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
-
- // 4xx: Client Error - The request contains bad syntax or cannot be
- // fulfilled
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed',
-
- // 5xx: Server Error - The server failed to fulfill an apparently
- // valid request
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported',
- 509 => 'Bandwidth Limit Exceeded',
-
- );
-
- /**
- * Returns the default reason phrase for the given code or all reason phrases
- *
- * @param int $code Response code
- * @return string|array|null Default reason phrase for $code if $code is given
- * (null if no phrase is available), array of all
- * reason phrases if $code is null
- * @link http://pear.php.net/bugs/18716
- */
- public static function getDefaultReasonPhrase($code = null)
- {
- if (null === $code) {
- return self::$phrases;
- } else {
- return isset(self::$phrases[$code]) ? self::$phrases[$code] : null;
- }
- }
-
- /**
- * Constructor, parses the response status line
- *
- * @param string Response status line (e.g. "HTTP/1.1 200 OK")
- * @param bool Whether body is still encoded by Content-Encoding
- * @param string Effective URL of the response
- * @throws HTTP_Request2_MessageException if status line is invalid according to spec
- */
- public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)
- {
- if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {
- throw new HTTP_Request2_MessageException(
- "Malformed response: {$statusLine}",
- HTTP_Request2_Exception::MALFORMED_RESPONSE
- );
- }
- $this->version = $m[1];
- $this->code = intval($m[2]);
- $this->reasonPhrase = !empty($m[3]) ? trim($m[3]) : self::getDefaultReasonPhrase($this->code);
- $this->bodyEncoded = (bool)$bodyEncoded;
- $this->effectiveUrl = (string)$effectiveUrl;
- }
-
- /**
- * Parses the line from HTTP response filling $headers array
- *
- * The method should be called after reading the line from socket or receiving
- * it into cURL callback. Passing an empty string here indicates the end of
- * response headers and triggers additional processing, so be sure to pass an
- * empty string in the end.
- *
- * @param string Line from HTTP response
- */
- public function parseHeaderLine($headerLine)
- {
- $headerLine = trim($headerLine, "\r\n");
-
- // empty string signals the end of headers, process the received ones
- if ('' == $headerLine) {
- if (!empty($this->headers['set-cookie'])) {
- $cookies = is_array($this->headers['set-cookie'])?
- $this->headers['set-cookie']:
- array($this->headers['set-cookie']);
- foreach ($cookies as $cookieString) {
- $this->parseCookie($cookieString);
- }
- unset($this->headers['set-cookie']);
- }
- foreach (array_keys($this->headers) as $k) {
- if (is_array($this->headers[$k])) {
- $this->headers[$k] = implode(', ', $this->headers[$k]);
- }
- }
-
- // string of the form header-name: header value
- } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {
- $name = strtolower($m[1]);
- $value = trim($m[2]);
- if (empty($this->headers[$name])) {
- $this->headers[$name] = $value;
- } else {
- if (!is_array($this->headers[$name])) {
- $this->headers[$name] = array($this->headers[$name]);
- }
- $this->headers[$name][] = $value;
- }
- $this->lastHeader = $name;
-
- // continuation of a previous header
- } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {
- if (!is_array($this->headers[$this->lastHeader])) {
- $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);
- } else {
- $key = count($this->headers[$this->lastHeader]) - 1;
- $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);
- }
- }
- }
-
- /**
- * Parses a Set-Cookie header to fill $cookies array
- *
- * @param string value of Set-Cookie header
- * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html
- */
- protected function parseCookie($cookieString)
- {
- $cookie = array(
- 'expires' => null,
- 'domain' => null,
- 'path' => null,
- 'secure' => false
- );
-
- // Only a name=value pair
- if (!strpos($cookieString, ';')) {
- $pos = strpos($cookieString, '=');
- $cookie['name'] = trim(substr($cookieString, 0, $pos));
- $cookie['value'] = trim(substr($cookieString, $pos + 1));
-
- // Some optional parameters are supplied
- } else {
- $elements = explode(';', $cookieString);
- $pos = strpos($elements[0], '=');
- $cookie['name'] = trim(substr($elements[0], 0, $pos));
- $cookie['value'] = trim(substr($elements[0], $pos + 1));
-
- for ($i = 1; $i < count($elements); $i++) {
- if (false === strpos($elements[$i], '=')) {
- $elName = trim($elements[$i]);
- $elValue = null;
- } else {
- list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
- }
- $elName = strtolower($elName);
- if ('secure' == $elName) {
- $cookie['secure'] = true;
- } elseif ('expires' == $elName) {
- $cookie['expires'] = str_replace('"', '', $elValue);
- } elseif ('path' == $elName || 'domain' == $elName) {
- $cookie[$elName] = urldecode($elValue);
- } else {
- $cookie[$elName] = $elValue;
- }
- }
- }
- $this->cookies[] = $cookie;
- }
-
- /**
- * Appends a string to the response body
- * @param string
- */
- public function appendBody($bodyChunk)
- {
- $this->body .= $bodyChunk;
- }
-
- /**
- * Returns the effective URL of the response
- *
- * This may be different from the request URL if redirects were followed.
- *
- * @return string
- * @link http://pear.php.net/bugs/bug.php?id=18412
- */
- public function getEffectiveUrl()
- {
- return $this->effectiveUrl;
- }
-
- /**
- * Returns the status code
- * @return integer
- */
- public function getStatus()
- {
- return $this->code;
- }
-
- /**
- * Returns the reason phrase
- * @return string
- */
- public function getReasonPhrase()
- {
- return $this->reasonPhrase;
- }
-
- /**
- * Whether response is a redirect that can be automatically handled by HTTP_Request2
- * @return bool
- */
- public function isRedirect()
- {
- return in_array($this->code, array(300, 301, 302, 303, 307))
- && isset($this->headers['location']);
- }
-
- /**
- * Returns either the named header or all response headers
- *
- * @param string Name of header to return
- * @return string|array Value of $headerName header (null if header is
- * not present), array of all response headers if
- * $headerName is null
- */
- public function getHeader($headerName = null)
- {
- if (null === $headerName) {
- return $this->headers;
- } else {
- $headerName = strtolower($headerName);
- return isset($this->headers[$headerName])? $this->headers[$headerName]: null;
- }
- }
-
- /**
- * Returns cookies set in response
- *
- * @return array
- */
- public function getCookies()
- {
- return $this->cookies;
- }
-
- /**
- * Returns the body of the response
- *
- * @return string
- * @throws HTTP_Request2_Exception if body cannot be decoded
- */
- public function getBody()
- {
- if (0 == strlen($this->body) || !$this->bodyEncoded ||
- !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))
- ) {
- return $this->body;
-
- } else {
- if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
- $oldEncoding = mb_internal_encoding();
- mb_internal_encoding('iso-8859-1');
- }
-
- try {
- switch (strtolower($this->getHeader('content-encoding'))) {
- case 'gzip':
- $decoded = self::decodeGzip($this->body);
- break;
- case 'deflate':
- $decoded = self::decodeDeflate($this->body);
- }
- } catch (Exception $e) {
- }
-
- if (!empty($oldEncoding)) {
- mb_internal_encoding($oldEncoding);
- }
- if (!empty($e)) {
- throw $e;
- }
- return $decoded;
- }
- }
-
- /**
- * Get the HTTP version of the response
- *
- * @return string
- */
- public function getVersion()
- {
- return $this->version;
- }
-
- /**
- * Decodes the message-body encoded by gzip
- *
- * The real decoding work is done by gzinflate() built-in function, this
- * method only parses the header and checks data for compliance with
- * RFC 1952
- *
- * @param string gzip-encoded data
- * @return string decoded data
- * @throws HTTP_Request2_LogicException
- * @throws HTTP_Request2_MessageException
- * @link http://tools.ietf.org/html/rfc1952
- */
- public static function decodeGzip($data)
- {
- $length = strlen($data);
- // If it doesn't look like gzip-encoded data, don't bother
- if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
- return $data;
- }
- if (!function_exists('gzinflate')) {
- throw new HTTP_Request2_LogicException(
- 'Unable to decode body: gzip extension not available',
- HTTP_Request2_Exception::MISCONFIGURATION
- );
- }
- $method = ord(substr($data, 2, 1));
- if (8 != $method) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: unknown compression method',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $flags = ord(substr($data, 3, 1));
- if ($flags & 224) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: reserved bits are set',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
-
- // header is 10 bytes minimum. may be longer, though.
- $headerLength = 10;
- // extra fields, need to skip 'em
- if ($flags & 4) {
- if ($length - $headerLength - 2 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $extraLength = unpack('v', substr($data, 10, 2));
- if ($length - $headerLength - 2 - $extraLength[1] < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $headerLength += $extraLength[1] + 2;
- }
- // file name, need to skip that
- if ($flags & 8) {
- if ($length - $headerLength - 1 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $filenameLength = strpos(substr($data, $headerLength), chr(0));
- if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $headerLength += $filenameLength + 1;
- }
- // comment, need to skip that also
- if ($flags & 16) {
- if ($length - $headerLength - 1 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $commentLength = strpos(substr($data, $headerLength), chr(0));
- if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $headerLength += $commentLength + 1;
- }
- // have a CRC for header. let's check
- if ($flags & 2) {
- if ($length - $headerLength - 2 < 8) {
- throw new HTTP_Request2_MessageException(
- 'Error parsing gzip header: data too short',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $crcReal = 0xffff & crc32(substr($data, 0, $headerLength));
- $crcStored = unpack('v', substr($data, $headerLength, 2));
- if ($crcReal != $crcStored[1]) {
- throw new HTTP_Request2_MessageException(
- 'Header CRC check failed',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- $headerLength += 2;
- }
- // unpacked data CRC and size at the end of encoded data
- $tmp = unpack('V2', substr($data, -8));
- $dataCrc = $tmp[1];
- $dataSize = $tmp[2];
-
- // finally, call the gzinflate() function
- // don't pass $dataSize to gzinflate, see bugs #13135, #14370
- $unpacked = gzinflate(substr($data, $headerLength, -8));
- if (false === $unpacked) {
- throw new HTTP_Request2_MessageException(
- 'gzinflate() call failed',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- } elseif ($dataSize != strlen($unpacked)) {
- throw new HTTP_Request2_MessageException(
- 'Data size check failed',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
- throw new HTTP_Request2_Exception(
- 'Data CRC check failed',
- HTTP_Request2_Exception::DECODE_ERROR
- );
- }
- return $unpacked;
- }
-
- /**
- * Decodes the message-body encoded by deflate
- *
- * @param string deflate-encoded data
- * @return string decoded data
- * @throws HTTP_Request2_LogicException
- */
- public static function decodeDeflate($data)
- {
- if (!function_exists('gzuncompress')) {
- throw new HTTP_Request2_LogicException(
- 'Unable to decode body: gzip extension not available',
- HTTP_Request2_Exception::MISCONFIGURATION
- );
- }
- // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,
- // while many applications send raw deflate stream from RFC 1951.
- // We should check for presence of zlib header and use gzuncompress() or
- // gzinflate() as needed. See bug #15305
- $header = unpack('n', substr($data, 0, 2));
- return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);
- }
-}
-?>
\ No newline at end of file
diff --git a/lib/ext/Mail.php b/lib/ext/Mail.php
deleted file mode 100755
index 75132ac..0000000
--- a/lib/ext/Mail.php
+++ /dev/null
@@ -1,270 +0,0 @@
-<?php
-/**
- * PEAR's Mail:: interface.
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2002-2007, 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.
- *
- * @category Mail
- * @package Mail
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @copyright 1997-2010 Chuck Hagenbuch
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-require_once 'PEAR.php';
-
-/**
- * PEAR's Mail:: interface. Defines the interface for implementing
- * mailers under the PEAR hierarchy, and provides supporting functions
- * useful in multiple mailer backends.
- *
- * @access public
- * @version $Revision: 294747 $
- * @package Mail
- */
-class Mail
-{
- /**
- * Line terminator used for separating header lines.
- * @var string
- */
- var $sep = "\r\n";
-
- /**
- * Provides an interface for generating Mail:: objects of various
- * types
- *
- * @param string $driver The kind of Mail:: object to instantiate.
- * @param array $params The parameters to pass to the Mail:: object.
- * @return object Mail a instance of the driver class or if fails a PEAR Error
- * @access public
- */
- function &factory($driver, $params = array())
- {
- $driver = strtolower($driver);
- @include_once 'Mail/' . $driver . '.php';
- $class = 'Mail_' . $driver;
- if (class_exists($class)) {
- $mailer = new $class($params);
- return $mailer;
- } else {
- return PEAR::raiseError('Unable to find class for driver ' . $driver);
- }
- }
-
- /**
- * Implements 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
- * @deprecated use Mail_mail::send instead
- */
- 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']);
- }
-
- // flatten the headers out.
- list(, $text_headers) = Mail::prepareHeaders($headers);
-
- return mail($recipients, $subject, $body, $text_headers);
- }
-
- /**
- * Sanitize an array of mail headers by removing any additional header
- * strings present in a legitimate header's value. The goal of this
- * filter is to prevent mail injection attacks.
- *
- * @param array $headers The associative array of headers to sanitize.
- *
- * @access private
- */
- function _sanitizeHeaders(&$headers)
- {
- foreach ($headers as $key => $value) {
- $headers[$key] =
- preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
- null, $value);
- }
- }
-
- /**
- * Take an array of mail headers and return a string containing
- * text usable in sending a message.
- *
- * @param array $headers The array of headers to prepare, 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'.
- *
- * @return mixed Returns false if it encounters a bad address,
- * otherwise returns an array containing two
- * elements: Any From: address found in the headers,
- * and the plain text version of the headers.
- * @access private
- */
- function prepareHeaders($headers)
- {
- $lines = array();
- $from = null;
-
- foreach ($headers as $key => $value) {
- if (strcasecmp($key, 'From') === 0) {
- include_once 'Mail/RFC822.php';
- $parser = new Mail_RFC822();
- $addresses = $parser->parseAddressList($value, 'localhost', false);
- if (is_a($addresses, 'PEAR_Error')) {
- return $addresses;
- }
-
- $from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
-
- // Reject envelope From: addresses with spaces.
- if (strstr($from, ' ')) {
- return false;
- }
-
- $lines[] = $key . ': ' . $value;
- } elseif (strcasecmp($key, 'Received') === 0) {
- $received = array();
- if (is_array($value)) {
- foreach ($value as $line) {
- $received[] = $key . ': ' . $line;
- }
- }
- else {
- $received[] = $key . ': ' . $value;
- }
- // Put Received: headers at the top. Spam detectors often
- // flag messages with Received: headers after the Subject:
- // as spam.
- $lines = array_merge($received, $lines);
- } else {
- // If $value is an array (i.e., a list of addresses), convert
- // it to a comma-delimited string of its elements (addresses).
- if (is_array($value)) {
- $value = implode(', ', $value);
- }
- $lines[] = $key . ': ' . $value;
- }
- }
-
- return array($from, join($this->sep, $lines));
- }
-
- /**
- * Take a set of recipients and parse them, returning an array of
- * bare addresses (forward paths) that can be passed to sendmail
- * or an smtp server with the rcpt to: command.
- *
- * @param mixed Either a comma-seperated list of recipients
- * (RFC822 compliant), or an array of recipients,
- * each RFC822 valid.
- *
- * @return mixed An array of forward paths (bare addresses) or a PEAR_Error
- * object if the address list could not be parsed.
- * @access private
- */
- function parseRecipients($recipients)
- {
- include_once 'Mail/RFC822.php';
-
- // if we're passed an array, assume addresses are valid and
- // implode them before parsing.
- if (is_array($recipients)) {
- $recipients = implode(', ', $recipients);
- }
-
- // Parse recipients, leaving out all personal info. This is
- // for smtp recipients, etc. All relevant personal information
- // should already be in the headers.
- $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false);
-
- // If parseAddressList() returned a PEAR_Error object, just return it.
- if (is_a($addresses, 'PEAR_Error')) {
- return $addresses;
- }
-
- $recipients = array();
- if (is_array($addresses)) {
- foreach ($addresses as $ob) {
- $recipients[] = $ob->mailbox . '@' . $ob->host;
- }
- }
-
- return $recipients;
- }
-
-}
diff --git a/lib/ext/Mail/RFC822.php b/lib/ext/Mail/RFC822.php
deleted file mode 100755
index 58d3646..0000000
--- a/lib/ext/Mail/RFC822.php
+++ /dev/null
@@ -1,951 +0,0 @@
-<?php
-/**
- * RFC 822 Email address list validation Utility
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2001-2010, 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.
- *
- * @category Mail
- * @package Mail
- * @author Richard Heyes <richard@phpguru.org>
- * @author Chuck Hagenbuch <chuck@horde.org
- * @copyright 2001-2010 Richard Heyes
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: RFC822.php 294749 2010-02-08 08:22:25Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-/**
- * 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: 294749 $
- * @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;
-
- // +2 is for the brackets
- $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2);
- } 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;
- }
- }
-
-}
diff --git a/lib/ext/Mail/mail.php b/lib/ext/Mail/mail.php
deleted file mode 100755
index a8b4b5d..0000000
--- a/lib/ext/Mail/mail.php
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-/**
- * internal PHP-mail() implementation of the PEAR Mail:: interface.
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2010 Chuck Hagenbuch
- * 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.
- *
- * @category Mail
- * @package Mail
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @copyright 2010 Chuck Hagenbuch
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-/**
- * internal PHP-mail() implementation of the PEAR Mail:: interface.
- * @package Mail
- * @version $Revision: 294747 $
- */
-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;
- }
-
-}
diff --git a/lib/ext/Mail/mock.php b/lib/ext/Mail/mock.php
deleted file mode 100755
index 61570ba..0000000
--- a/lib/ext/Mail/mock.php
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-/**
- * Mock implementation
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2010 Chuck Hagenbuch
- * 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.
- *
- * @category Mail
- * @package Mail
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @copyright 2010 Chuck Hagenbuch
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-/**
- * Mock implementation of the PEAR Mail:: interface for testing.
- * @access public
- * @package Mail
- * @version $Revision: 294747 $
- */
-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;
- }
-
-}
diff --git a/lib/ext/Mail/null.php b/lib/ext/Mail/null.php
deleted file mode 100755
index f8d5827..0000000
--- a/lib/ext/Mail/null.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * Null implementation of the PEAR Mail interface
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2010 Phil Kernick
- * 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.
- *
- * @category Mail
- * @package Mail
- * @author Phil Kernick <philk@rotfl.com.au>
- * @copyright 2010 Phil Kernick
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-/**
- * Null implementation of the PEAR Mail:: interface.
- * @access public
- * @package Mail
- * @version $Revision: 294747 $
- */
-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;
- }
-
-}
diff --git a/lib/ext/Mail/sendmail.php b/lib/ext/Mail/sendmail.php
deleted file mode 100755
index b056575..0000000
--- a/lib/ext/Mail/sendmail.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?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: 294744 $
- */
-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 = implode(' ', array_map('escapeshellarg', $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 = escapeshellarg($from); // Security bug #16200
-
- $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;
- }
-
-}
diff --git a/lib/ext/Mail/smtp.php b/lib/ext/Mail/smtp.php
deleted file mode 100755
index 52ea602..0000000
--- a/lib/ext/Mail/smtp.php
+++ /dev/null
@@ -1,444 +0,0 @@
-<?php
-/**
- * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
- *
- * PHP versions 4 and 5
- *
- * LICENSE:
- *
- * Copyright (c) 2010, Chuck Hagenbuch
- * 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.
- *
- * @category HTTP
- * @package HTTP_Request
- * @author Jon Parise <jon@php.net>
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @copyright 2010 Chuck Hagenbuch
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/Mail/
- */
-
-/** 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: 294747 $
- */
-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);
- list(,$args) = $this->_smtp->getResponse();
-
- if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
- $this->queued_as = $queued[1];
- }
-
- /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to.
- * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */
- $this->greeting = $this->_smtp->getGreeting();
-
- 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)]";
- }
-
-}
diff --git a/lib/ext/Mail/smtpmx.php b/lib/ext/Mail/smtpmx.php
deleted file mode 100755
index f0b6940..0000000
--- a/lib/ext/Mail/smtpmx.php
+++ /dev/null
@@ -1,502 +0,0 @@
-<?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:
- *
- * Copyright (c) 2010, gERD Schaufelberger
- * 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.
- *
- * @category Mail
- * @package Mail_smtpmx
- * @author gERD Schaufelberger <gerd@php-tools.net>
- * @copyright 2010 gERD Schaufelberger
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $
- * @link http://pear.php.net/package/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: 294747 $
- */
-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);
- }
-
-}
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
deleted file mode 100644
index 30f40df..0000000
--- a/lib/ext/Net/LDAP3.php
+++ /dev/null
@@ -1,3118 +0,0 @@
-<?php
-/*
- +-----------------------------------------------------------------------+
- | Net/LDAP3.php |
- | |
- | Based on code created by the Roundcube Webmail team. |
- | |
- | Copyright (C) 2006-2014, The Roundcube Dev Team |
- | Copyright (C) 2012-2014, Kolab Systems AG |
- | |
- | This program is free software: you can redistribute it and/or modify |
- | it under the terms of the GNU General Public License as published by |
- | the Free Software Foundation, either version 3 of the License, or |
- | (at your option) any later version. |
- | |
- | This program is distributed in the hope that it will be useful, |
- | but WITHOUT ANY WARRANTY; without even the implied warranty of |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
- | GNU General Public License for more details. |
- | |
- | You should have received a copy of the GNU General Public License |
- | along with this program. If not, see <http://www.gnu.org/licenses/>. |
- | |
- | PURPOSE: |
- | Provide advanced functionality for accessing LDAP directories |
- | |
- +-----------------------------------------------------------------------+
- | Authors: Thomas Bruederli <roundcube@gmail.com> |
- | Aleksander Machniak <machniak@kolabsys.com> |
- | Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
- +-----------------------------------------------------------------------+
-*/
-
-require_once __DIR__ . '/LDAP3/Result.php';
-
-/**
- * Model class to access a LDAP directories
- *
- * @package Net_LDAP3
- */
-class Net_LDAP3
-{
- public $conn;
- public $vlv_active = false;
-
- private $attribute_level_rights_map = array(
- "r" => "read",
- "s" => "search",
- "w" => "write",
- "o" => "delete",
- "c" => "compare",
- "W" => "write",
- "O" => "delete"
- );
-
- private $entry_level_rights_map = array(
- "a" => "add",
- "d" => "delete",
- "n" => "modrdn",
- "v" => "read"
- );
-
- /*
- * Manipulate configuration through the config_set and config_get methods.
- * Available options:
- * 'debug' => false,
- * 'hosts' => array(),
- * 'port' => 389,
- * 'use_tls' => false,
- * 'ldap_version' => 3, // using LDAPv3
- * 'auth_method' => '', // SASL authentication method (for proxy auth), e.g. DIGEST-MD5
- * 'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting
- * 'referrals' => false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
- * 'network_timeout' => 10, // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x
- * 'sizelimit' => 0, // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
- * 'timelimit' => 0, // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
- * 'vlv' => false, // force VLV off
- * 'config_root_dn' => 'cn=config', // Root DN to read config (e.g. vlv indexes) from
- * 'service_bind_dn' => 'uid=kolab-service,ou=Special Users,dc=example,dc=org',
- * 'service_bind_pw' => 'Welcome2KolabSystems',
- * 'root_dn' => 'dc=example,dc=org',
- */
- protected $config = array(
- 'sizelimit' => 0,
- 'timelimit' => 0,
- );
-
- protected $debug_level = false;
- protected $list_page = 1;
- protected $page_size = 10;
- protected $icache = array();
- protected $cache;
-
- // Use public method config_set('log_hook', $callback) to have $callback be
- // call_user_func'ed instead of the local log functions.
- protected $_log_hook;
-
- // Use public method config_set('config_get_hook', $callback) to have
- // $callback be call_user_func'ed instead of the local config_get function.
- protected $_config_get_hook;
-
- // Use public method config_set('config_set_hook', $callback) to have
- // $callback be call_user_func'ed instead of the local config_set function.
- protected $_config_set_hook;
-
- // Not Yet Implemented
- // Intended to allow hooking in for the purpose of caching.
- protected $_result_hook;
-
- // Runtime. These are not the variables you're looking for.
- protected $_current_bind_dn;
- protected $_current_bind_pw;
- protected $_current_host;
- protected $_supported_control = array();
- protected $_vlv_indexes_and_searches;
-
- /**
- * Constructor
- *
- * @param array $config Configuration parameters that have not already
- * been initialized. For configuration parameters
- * that have in fact been set, use the config_set()
- * method after initialization.
- */
- public function __construct($config = array())
- {
- if (!empty($config) && is_array($config)) {
- foreach ($config as $key => $value) {
- if (empty($this->config[$key])) {
- $setter = 'config_set_' . $key;
- if (method_exists($this, $setter)) {
- $this->$setter($value);
- }
- else if (isset($this->$key)) {
- $this->$key = $value;
- }
- else {
- $this->config[$key] = $value;
- }
- }
- }
- }
- }
-
- /**
- * Add multiple entries to the directory information tree in one go.
- */
- public function add_entries($entries, $attributes = array())
- {
- // If $entries is an associative array, it's keys are DNs and its
- // values are the attributes for that DN.
- //
- // If $entries is a non-associative array, the attributes are expected
- // to be positional in $attributes.
-
- $result_set = array();
-
- if (array_keys($entries) == range(0, count($entries) - 1)) {
- // $entries is sequential
- if (count($entries) !== count($attributes)) {
- $this->_error("Wrong entry/attribute count in " . __FUNCTION__);
- return false;
- }
-
- for ($i = 0; $i < count($entries); $i++) {
- $result_set[$i] = $this->add_entry($entries[$i], $attributes[$i]);
- }
- }
- else {
- // $entries is associative
- foreach ($entries as $entry_dn => $entry_attributes) {
- if (array_keys($attributes) !== range(0, count($attributes)-1)) {
- // $attributes is associative as well, let's merge these
- //
- // $entry_attributes takes precedence, so is in the second
- // position in array_merge()
- $entry_attributes = array_merge($attributes, $entry_attributes);
- }
-
- $result_set[$entry_dn] = $this->add_entry($entry_dn, $entry_attributes);
- }
- }
-
- return $result_set;
- }
-
- /**
- * Add an entry to the directory information tree.
- */
- public function add_entry($entry_dn, $attributes)
- {
- // TODO:
- // - Get entry rdn attribute value from entry_dn and see if it exists in
- // attributes -> issue warning if so (but not block the operation).
- $this->_debug("Entry DN", $entry_dn);
- $this->_debug("Attributes", $attributes);
-
- foreach ($attributes as $attr_name => $attr_value) {
- if (empty($attr_value)) {
- unset($attributes[$attr_name]);
- }
- }
-
- $this->_debug("C: Add $entry_dn: " . json_encode($attributes));
-
- if (($add_result = ldap_add($this->conn, $entry_dn, $attributes)) == false) {
- $this->_debug("S: " . ldap_error($this->conn));
- $this->_debug("S: Adding entry $entry_dn failed. " . ldap_error($this->conn));
-
- return false;
- }
-
- $this->_debug("LDAP: S: OK");
-
- return true;
- }
-
- /**
- * Add replication agreements and initialize the consumer(s) for
- * $domain_root_dn.
- *
- * Searches the configured replicas for any of the current domain/config
- * databases, and uses this information to configure the additional
- * replication for the (new) domain database (at $domain_root_dn).
- *
- * Very specific to Netscape-based directory servers, and currently also
- * very specific to multi-master replication.
- */
- public function add_replication_agreements($domain_root_dn)
- {
- $replica_hosts = $this->list_replicas();
-
- if (empty($replica_hosts)) {
- return;
- }
-
- $result = $this->search($this->config_get('config_root_dn'), "(&(objectclass=nsDS5Replica)(nsDS5ReplicaType=3))", "sub");
-
- if (!$result) {
- $this->_debug("No replication configuration found.");
- return;
- }
-
- // Preserve the number of replicated databases we have, because the replication ID
- // can be calculated from the number of databases replicated, and the number of
- // servers.
- $num_replica_dbs = $result->count();
- $replicas = $result->entries(true);
- $max_replica_agreements = 0;
-
- foreach ($replicas as $replica_dn => $replica_attrs) {
- $result = $this->search($replica_dn, "(objectclass=nsDS5ReplicationAgreement)", "sub");
- if ($result) {
- if ($max_replica_agreements < $result->count()) {
- $max_replica_agreements = $result->count();
- $max_replica_agreements_dn = $replica_dn;
- }
- }
- }
-
- $max_repl_id = $num_replica_dbs * count($replica_hosts);
-
- $this->_debug("The current maximum replication ID is $max_repl_id");
- $this->_debug("The current maximum number of replication agreements for any database is $max_replica_agreements (for $max_replica_agreements_dn)");
- $this->_debug("With " . count($replica_hosts) . " replicas, the next is " . ($max_repl_id + 1) . " and the last one is " . ($max_repl_id + count($replica_hosts)));
-
- // Then add the replication agreements
- foreach ($replica_hosts as $num => $replica_host) {
- $ldap = new Net_LDAP3($this->config);
- $ldap->config_set('hosts', array($replica_host));
- $ldap->connect();
- $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);
-
- $replica_attrs = array(
- 'cn' => 'replica',
- 'objectclass' => array(
- 'top',
- 'nsds5replica',
- 'extensibleobject',
- ),
- 'nsDS5ReplicaBindDN' => $ldap->get_entry_attribute($replica_dn, "nsDS5ReplicaBindDN"),
- 'nsDS5ReplicaId' => ($max_repl_id + $num + 1),
- 'nsDS5ReplicaRoot' => $domain_root_dn,
- 'nsDS5ReplicaType' => $ldap->get_entry_attribute($replica_dn, "nsDS5ReplicaType"),
- 'nsds5ReplicaPurgeDelay' => $ldap->get_entry_attribute($replica_dn, "nsds5ReplicaPurgeDelay"),
- 'nsDS5Flags' => $ldap->get_entry_attribute($replica_dn, "nsDS5Flags")
- );
-
- $new_replica_dn = 'cn=replica,cn="' . $domain_root_dn . '",cn=mapping tree,cn=config';
-
- $this->_debug("Adding $new_replica_dn to $replica_host with attributes: " . var_export($replica_attrs, true));
-
- $result = $ldap->add_entry($new_replica_dn, $replica_attrs);
-
- if (!$result) {
- $this->_error("Could not add replication configuration to database for $domain_root_dn on $replica_host");
- continue;
- }
-
- $result = $ldap->search($replica_dn, "(objectclass=nsDS5ReplicationAgreement)", "sub");
-
- if (!$result) {
- $this->_error("Host $replica_host does not have any replication agreements");
- continue;
- }
-
- $entries = $result->entries(true);
- $replica_agreement_tpl_dn = key($entries);
-
- $this->_debug("Using " . var_export($replica_agreement_tpl_dn, true) . " as the template for new replication agreements");
-
- foreach ($replica_hosts as $replicate_to_host) {
- // Skip the current server
- if ($replicate_to_host == $replica_host) {
- continue;
- }
-
- $this->_debug("Adding a replication agreement for $domain_root_dn to $replicate_to_host on " . $replica_host);
-
- $attrs = array(
- 'objectclass',
- 'nsDS5ReplicaBindDN',
- 'nsDS5ReplicaCredentials',
- 'nsDS5ReplicaTransportInfo',
- 'nsDS5ReplicaBindMethod',
- 'nsDS5ReplicaHost',
- 'nsDS5ReplicaPort'
- );
-
- $replica_agreement_attrs = $ldap->get_entry_attributes($replica_agreement_tpl_dn, $attrs);
- $replica_agreement_attrs['cn'] = array_shift(explode('.', $replicate_to_host)) . str_replace(array('dc=',','), array('_',''), $domain_root_dn);
- $replica_agreement_attrs['nsDS5ReplicaRoot'] = $domain_root_dn;
- $replica_agreement_dn = "cn=" . $replica_agreement_attrs['cn'] . "," . $new_replica_dn;
-
- $this->_debug("Adding $replica_agreement_dn to $replica_host with attributes: " . var_export($replica_agreement_attrs, true));
-
- $result = $ldap->add_entry($replica_agreement_dn, $replica_agreement_attrs);
-
- if (!$result) {
- $this->_error("Failed adding $replica_agreement_dn");
- }
- }
- }
-
- $server_id = implode('', array_diff($replica_hosts, $this->_server_id_not));
-
- $this->_debug("About to trigger consumer initialization for replicas on current 'parent': $server_id");
-
- $result = $this->search($this->config_get('config_root_dn'), "(&(objectclass=nsDS5ReplicationAgreement)(nsds5replicaroot=$domain_root_dn))", "sub");
-
- if ($result) {
- foreach ($result->entries(true) as $agreement_dn => $agreement_attrs) {
- $this->modify_entry_attributes(
- $agreement_dn,
- array(
- 'replace' => array(
- 'nsds5BeginReplicaRefresh' => 'start',
- ),
- )
- );
- }
- }
- }
-
- public function attribute_details($attributes = array())
- {
- $schema = $this->init_schema();
-
- if (!$schema) {
- return array();
- }
-
- $attribs = $schema->getAll('attributes');
-
- $attributes_details = array();
-
- foreach ($attributes as $attribute) {
- if (array_key_exists($attribute, $attribs)) {
- $attrib_details = $attribs[$attribute];
-
- if (!empty($attrib_details['sup'])) {
- foreach ($attrib_details['sup'] as $super_attrib) {
- $_attrib_details = $attribs[$super_attrib];
- if (is_array($_attrib_details)) {
- $attrib_details = array_merge($_attrib_details, $attrib_details);
- }
- }
- }
- }
- else if (array_key_exists(strtolower($attribute), $attribs)) {
- $attrib_details = $attribs[strtolower($attribute)];
-
- if (!empty($attrib_details['sup'])) {
- foreach ($attrib_details['sup'] as $super_attrib) {
- $_attrib_details = $attribs[$super_attrib];
- if (is_array($_attrib_details)) {
- $attrib_details = array_merge($_attrib_details, $attrib_details);
- }
- }
- }
- }
- else {
- $this->_warning("LDAP: No schema details exist for attribute $attribute (which is strange)");
- }
-
- // The relevant parts only, please
- $attributes_details[$attribute] = array(
- 'type' => !empty($attrib_details['single-value']) ? 'text' : 'list',
- 'description' => $attrib_details['desc'],
- 'syntax' => $attrib_details['syntax'],
- 'max-length' => $attrib_details['max-length'] ?: false,
- );
- }
-
- return $attributes_details;
- }
-
- public function attributes_allowed($objectclasses = array())
- {
- $this->_debug("Listing allowed_attributes for objectclasses", $objectclasses);
-
- if (!is_array($objectclasses) || empty($objectclasses)) {
- return false;
- }
-
- $schema = $this->init_schema();
- if (!$schema) {
- return false;
- }
-
- $may = array();
- $must = array();
- $superclasses = array();
-
- foreach ($objectclasses as $objectclass) {
- $superclass = $schema->superclass($objectclass);
- if (!empty($superclass)) {
- $superclasses = array_merge($superclass, $superclasses);
- }
-
- $_may = $schema->may($objectclass);
- $_must = $schema->must($objectclass);
-
- if (is_array($_may)) {
- $may = array_merge($may, $_may);
- }
- if (is_array($_must)) {
- $must = array_merge($must, $_must);
- }
- }
-
- $may = array_unique($may);
- $must = array_unique($must);
- $superclasses = array_unique($superclasses);
-
- return array('may' => $may, 'must' => $must, 'super' => $superclasses);
- }
-
- public function classes_allowed()
- {
- $schema = $this->init_schema();
- if (!$schema) {
- return false;
- }
-
- $list = $schema->getAll('objectclasses');
- $classes = array();
-
- foreach ($list as $class) {
- $classes[] = $class['name'];
- }
-
- return $classes;
- }
-
- /**
- * Bind connection with DN and password
- *
- * @param string $dn Bind DN
- * @param string $pass Bind password
- *
- * @return boolean True on success, False on error
- */
- public function bind($bind_dn, $bind_pw)
- {
- if (!$this->conn) {
- return false;
- }
-
- if ($bind_dn == $this->_current_bind_dn) {
- return true;
- }
-
- $this->_debug("C: Bind [dn: $bind_dn]");
-
- if (@ldap_bind($this->conn, $bind_dn, $bind_pw)) {
- $this->_debug("S: OK");
- $this->_current_bind_dn = $bind_dn;
- $this->_current_bind_pw = $bind_pw;
-
- return true;
- }
-
- $this->_debug("S: ".ldap_error($this->conn));
- $this->_error("Bind failed for dn=$bind_dn: ".ldap_error($this->conn));
-
- return false;
- }
-
- /**
- * Close connection to LDAP server
- */
- public function close()
- {
- if ($this->conn) {
- $this->_debug("C: Close");
- ldap_unbind($this->conn);
-
- $this->_current_bind_dn = null;
- $this->_current_bind_pw = null;
- $this->conn = null;
- }
- }
-
- /**
- * Get the value of a configuration item.
- *
- * @param string $key Configuration key
- * @param mixed $default Default value to return
- */
- public function config_get($key, $default = null)
- {
- if (!empty($this->_config_get_hook)) {
- return call_user_func_array($this->_config_get_hook, array($key, $value));
- }
- else if (method_exists($this, "config_get_{$key}")) {
- return call_user_func(array($this, "config_get_$key"), $value);
- }
- else if (!isset($this->config[$key])) {
- return $default;
- }
- else {
- return $this->config[$key];
- }
- }
-
- /**
- * Set a configuration item to value.
- *
- * @param string $key Configuration key
- * @param mixed $value Configuration value
- */
- public function config_set($key, $value = null)
- {
- if (is_array($key)) {
- foreach ($key as $k => $v) {
- $this->config_set($k, $v);
- }
- return;
- }
-
- if (!empty($this->_config_set_hook)) {
- call_user_func($this->_config_set_hook, array($key, $value));
- }
- else if (method_exists($this, "config_set_{$key}")) {
- call_user_func_array(array($this, "config_set_$key"), array($value));
- }
- else if (property_exists($this, $key)) {
- $this->$key = $value;
- }
- else {
- // 'host' option is deprecated
- if ($key == 'host') {
- $this->config['hosts'] = (array) $value;
- }
- else {
- $this->config[$key] = $value;
- }
- }
- }
-
- /**
- * Establish a connection to the LDAP server
- */
- public function connect($host = null)
- {
- if (!function_exists('ldap_connect')) {
- $this->_error("No ldap support in this PHP installation");
- return false;
- }
-
- if (is_resource($this->conn)) {
- $this->_debug("Connection already exists");
- return true;
- }
-
- $hosts = !empty($host) ? $host : $this->config_get('hosts', array());
- $port = $this->config_get('port', 389);
-
- foreach ((array) $hosts as $host) {
- $this->_debug("C: Connect [$host:$port]");
-
- if ($lc = @ldap_connect($host, $port)) {
- if ($this->config_get('use_tls', false) === true) {
- if (!ldap_start_tls($lc)) {
- $this->_debug("S: Could not start TLS. " . ldap_error($lc));
- continue;
- }
- }
-
- $this->_debug("S: OK");
-
- $ldap_version = $this->config_get('ldap_version', 3);
- $timeout = $this->config_get('network_timeout');
- $referrals = $this->config_get('referrals');
-
- ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
-
- if ($timeout) {
- ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
- }
-
- if ($referrals !== null) {
- ldap_set_option($lc, LDAP_OPT_REFERRALS, (bool) $referrals);
- }
-
- $this->_current_host = $host;
- $this->conn = $lc;
-
- break;
- }
-
- $this->_debug("S: NOT OK");
- }
-
- if (!is_resource($this->conn)) {
- $this->_error("Could not connect to LDAP");
- return false;
- }
-
- return true;
- }
-
- /**
- * Shortcut to ldap_delete()
- */
- public function delete_entry($entry_dn)
- {
- $this->_debug("LDAP: C: Delete $entry_dn");
-
- if (ldap_delete($this->conn, $entry_dn) === false) {
- $this->_debug("LDAP: S: " . ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("LDAP: S: OK");
-
- return true;
- }
-
- /**
- * Deletes specified entry and all entries in the tree
- */
- public function delete_entry_recursive($entry_dn)
- {
- // searching for sub entries, but not scope sub, just one level
- $result = $this->search($entry_dn, '(objectclass=*)', 'one');
-
- if ($result) {
- $entries = $result->entries(true);
-
- foreach (array_keys($entries) as $sub_dn) {
- if (!$this->delete_entry_recursive($sub_dn)) {
- return false;
- }
- }
- }
-
- if (!$this->delete_entry($entry_dn)) {
- return false;
- }
-
- return true;
- }
-
- public function effective_rights($subject)
- {
- $effective_rights_control_oid = "1.3.6.1.4.1.42.2.27.9.5.2";
-
- $supported_controls = $this->supported_controls();
-
- if (!in_array($effective_rights_control_oid, $supported_controls)) {
- $this->_debug("LDAP: No getEffectiveRights control in supportedControls");
- return false;
- }
-
- $attributes = array(
- 'attributeLevelRights' => array(),
- 'entryLevelRights' => array(),
- );
-
- $entry_dn = $this->entry_dn($subject);
-
- if (!$entry_dn) {
- $entry_dn = $this->config_get($subject . "_base_dn");
- }
-
- if (!$entry_dn) {
- $entry_dn = $this->config_get("base_dn");
- }
-
- if (!$entry_dn) {
- $entry_dn = $this->config_get("root_dn");
- }
-
- $this->_debug("effective_rights for subject $subject resolves to entry dn $entry_dn");
-
- $moz_ldapsearch = "/usr/lib64/mozldap/ldapsearch";
- if (!is_file($moz_ldapsearch)) {
- $moz_ldapsearch = "/usr/lib/mozldap/ldapsearch";
- }
- if (!is_file($moz_ldapsearch)) {
- $moz_ldapsearch = null;
- }
-
- if (empty($moz_ldapsearch)) {
- $this->_error("Mozilla LDAP C SDK binary ldapsearch not found, cannot get effective rights on subject $subject");
- return null;
- }
-
- $output = array();
- $command = Array(
- $moz_ldapsearch,
- '-x',
- '-h',
- $this->_current_host,
- '-p',
- $this->config_get('port', 389),
- '-b',
- escapeshellarg($entry_dn),
- '-s',
- 'base',
- '-D',
- escapeshellarg($this->_current_bind_dn),
- '-w',
- escapeshellarg($this->_current_bind_pw)
- );
-
- if ($this->vendor_name() == "Oracle Corporation") {
- // For Oracle DSEE
- $command[] = "-J";
- $command[] = escapeshellarg(
- implode(
- ':',
- Array(
- $effective_rights_control_oid, // OID
- 'true' // Criticality
- )
- )
- );
- $command[] = "-c";
- $command[] = escapeshellarg(
- 'dn:' . $this->_current_bind_dn
- );
-
- } else {
- // For 389 DS:
- $command[] = "-J";
- $command[] = escapeshellarg(
- implode(
- ':',
- Array(
- $effective_rights_control_oid, // OID
- 'true', // Criticality
- 'dn:' . $this->_current_bind_dn // User DN
- )
- )
- );
- }
-
- // For both
- $command[] = '"(objectclass=*)"';
- $command[] = '"*"';
-
- if ($this->vendor_name() == "Oracle Corporation") {
- // Oracle DSEE
- $command[] = 'aclRights';
- }
-
- // remove password from debug log
- $command_debug = $command;
- $command_debug[13] = '*';
-
- $command = implode(' ', $command);
- $command_debug = implode(' ', $command_debug);
-
- $this->_debug("LDAP: Executing command: $command_debug");
-
- exec($command, $output, $return_code);
-
- $this->_debug("LDAP: Command output:" . var_export($output, true));
- $this->_debug("Return code: " . $return_code);
-
- if ($return_code) {
- $this->_error("Command $moz_ldapsearch returned error code: $return_code");
- return null;
- }
-
- $lines = array();
- foreach ($output as $line_num => $line) {
- if (substr($line, 0, 1) == " ") {
- $lines[count($lines)-1] .= trim($line);
- }
- else {
- $lines[] = trim($line);
- }
- }
-
- if ($this->vendor_name() == "Oracle Corporation") {
- // Example for attribute level rights:
- // aclRights;attributeLevel;$attr:$right:$bool,$right:$bool
- // Example for entry level rights:
- // aclRights;entryLevel: add:1,delete:1,read:1,write:1,proxy:1
- foreach ($lines as $line) {
- $line_components = explode(':', $line);
- $attribute_name = explode(';', array_shift($line_components));
-
- switch ($attribute_name[0]) {
- case "aclRights":
- $this->parse_aclrights($attributes, $line);
- break;
- case "dn":
- $attributes[$attribute_name[0]] = trim(implode(';', $line_components));
- break;
- default:
- break;
- }
- }
-
- } else {
- foreach ($lines as $line) {
- $line_components = explode(':', $line);
- $attribute_name = array_shift($line_components);
- $attribute_value = trim(implode(':', $line_components));
-
- switch ($attribute_name) {
- case "attributeLevelRights":
- $attributes[$attribute_name] = $this->parse_attribute_level_rights($attribute_value);
- break;
- case "dn":
- $attributes[$attribute_name] = $attribute_value;
- break;
- case "entryLevelRights":
- $attributes[$attribute_name] = $this->parse_entry_level_rights($attribute_value);
- break;
- default:
- break;
- }
- }
- }
-
- return $attributes;
- }
-
- /**
- * Resolve entry data to entry DN
- *
- * @param string $subject Entry string (e.g. entry DN or unique attribute value)
- * @param array $attributes Additional attributes
- * @param string $base_dn Optional base DN
- *
- * @return string Entry DN string
- */
- public function entry_dn($subject, $attributes = array(), $base_dn = null)
- {
- $this->_debug("Net_LDAP3::entry_dn($subject)");
- $is_dn = ldap_explode_dn($subject, 1);
-
- if (is_array($is_dn) && array_key_exists("count", $is_dn) && $is_dn["count"] > 0) {
- $this->_debug("$subject is a dn");
- return $subject;
- }
-
- $this->_debug("$subject is not a dn");
-
- if (strlen($subject) < 32 || preg_match('/[^a-fA-F0-9-]/', $subject)) {
- $this->_debug("$subject is not a unique identifier");
- return;
- }
-
- $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
-
- $this->_debug("Using unique_attribute " . var_export($unique_attr, true) . " at " . __FILE__ . ":" . __LINE__);
-
- $attributes = array_merge(array($unique_attr => $subject), (array)$attributes);
- $subject = $this->entry_find_by_attribute($attributes, $base_dn);
-
- if (!empty($subject)) {
- return key($subject);
- }
- }
-
- public function entry_find_by_attribute($attributes, $base_dn = null)
- {
- $this->_debug("Net_LDAP3::entry_find_by_attribute(\$attributes, \$base_dn) called with base_dn", $base_dn, "and attributes", $attributes);
-
- if (empty($attributes) || !is_array($attributes)) {
- return false;
- }
-
- if (empty($attributes[key($attributes)])) {
- return false;
- }
-
- $filter = count($attributes) ? "(&" : "";
-
- foreach ($attributes as $key => $value) {
- $filter .= "(" . $key . "=" . $value . ")";
- }
-
- $filter .= count($attributes) ? ")" : "";
-
- if (empty($base_dn)) {
- $base_dn = $this->config_get('root_dn');
- $this->_debug("Using base_dn from domain " . $this->domain . ": " . $base_dn);
- }
-
- $result = $this->search($base_dn, $filter, 'sub', array_keys($attributes));
-
- if ($result && $result->count() > 0) {
- $this->_debug("Results found: " . implode(', ', array_keys($result->entries(true))));
- return $result->entries(true);
- }
- else {
- $this->_debug("No result");
- return false;
- }
- }
-
- public function find_user_groups($member_dn)
- {
- $groups = array();
- $root_dn = $this->config_get('root_dn');
-
- // TODO: Do not query for both, it's either one or the other
- $entries = $this->search($root_dn, "(|" .
- "(&(objectclass=groupofnames)(member=$member_dn))" .
- "(&(objectclass=groupofuniquenames)(uniquemember=$member_dn))" .
- ")"
- );
-
- if ($entries) {
- $groups = array_keys($entries->entries(true));
- }
-
- return $groups;
- }
-
- public function get_entry_attribute($subject_dn, $attribute)
- {
- $entry = $this->get_entry_attributes($subject_dn, (array)$attribute);
-
- return $entry[strtolower($attribute)];
- }
-
- public function get_entry_attributes($subject_dn, $attributes)
- {
- // @TODO: use get_entry?
- $result = $this->search($subject_dn, '(objectclass=*)', 'base', $attributes);
-
- if (!$result) {
- return array();
- }
-
- $entries = $result->entries(true);
- $entry_dn = key($entries);
- $entry = $entries[$entry_dn];
-
- return $entry;
- }
-
- /**
- * Get a specific LDAP entry, identified by its DN
- *
- * @param string $dn Record identifier
- * @param array $attributes Attributes to return
- *
- * @return array Hash array
- */
- public function get_entry($dn, $attributes = array())
- {
- $rec = null;
-
- if ($this->conn && $dn) {
- $this->_debug("C: Read [dn: $dn] [(objectclass=*)]");
-
- if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', $attributes)) {
- $this->_debug("S: OK");
-
- if ($entry = ldap_first_entry($this->conn, $ldap_result)) {
- $rec = ldap_get_attributes($this->conn, $entry);
- }
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- }
-
- if (!empty($rec)) {
- $rec['dn'] = $dn; // Add in the dn for the entry.
- }
- }
-
- return $rec;
- }
-
- public function list_replicas()
- {
- $this->_debug("Finding replicas for this server.");
-
- // Search any host that is a replica for the current host
- $replica_hosts = $this->config_get('replica_hosts', array());
- $root_dn = $this->config_get('config_root_dn');
-
- if (!empty($replica_hosts)) {
- return $replica_hosts;
- }
-
- $ldap = new Net_LDAP3($this->config);
- $ldap->connect();
- $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);
-
- $result = $ldap->search($root_dn, '(objectclass=nsds5replicationagreement)', 'sub', array('nsds5replicahost'));
-
- if (!$result) {
- $this->_debug("No replicas configured");
- return $replica_hosts;
- }
-
- $this->_debug("Replication agreements found: " . var_export($result->entries(true), true));
-
- foreach ($result->entries(true) as $dn => $attrs) {
- if (!in_array($attrs['nsds5replicahost'], $replica_hosts)) {
- $replica_hosts[] = $attrs['nsds5replicahost'];
- }
- }
-
- // $replica_hosts now holds the IDs of servers we are currently NOT
- // connected to. We might need this later in order to set
- $this->_server_id_not = $replica_hosts;
-
- $this->_debug("So far, we have the following replicas: " . var_export($replica_hosts, true));
-
- $ldap->close();
-
- foreach ($replica_hosts as $replica_host) {
- $ldap->config_set('hosts', array($replica_host));
- $ldap->connect();
- $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);
-
- $result = $ldap->search($root_dn, '(objectclass=nsds5replicationagreement)', 'sub', array('nsds5replicahost'));
- if (!$result) {
- $this->_debug("No replicas configured on $replica_host");
- $ldap->close();
- continue;
- }
-
- foreach ($result->entries(true) as $dn => $attrs) {
- if (!in_array($attrs['nsds5replicahost'], $replica_hosts)) {
- $replica_hosts[] = $attrs['nsds5replicahost'];
- }
- }
-
- $ldap->close();
- }
-
- $this->config_set('replica_hosts', $replica_hosts);
-
- return $replica_hosts;
- }
-
- public function login($username, $password, $domain = null, &$attributes = null)
- {
- $this->_debug("Net_LDAP3::login($username,***,$domain)");
-
- $_bind_dn = $this->config_get('service_bind_dn');
- $_bind_pw = $this->config_get('service_bind_pw');
-
- if (empty($_bind_dn)) {
- $this->_debug("No valid service bind dn found.");
- return null;
- }
-
- if (empty($_bind_pw)) {
- $this->_debug("No valid service bind password found.");
- return null;
- }
-
- $bound = $this->bind($_bind_dn, $_bind_pw);
-
- if (!$bound) {
- $this->_debug("Could not bind with service bind credentials.");
- return null;
- }
-
- $entry_dn = $this->entry_dn($username);
-
- if (!empty($entry_dn)) {
- $bound = $this->bind($entry_dn, $password);
-
- if (!$bound) {
- $this->_error("Could not bind with " . $entry_dn);
- return null;
- }
-
- // fetch user attributes if requested
- if (!empty($attributes)) {
- $attributes = $this->get_entry($entry_dn, $attributes);
- $attributes = self::normalize_entry($attributes, true);
- }
-
- return $entry_dn;
- }
-
- $base_dn = $this->config_get('root_dn');
-
- if (empty($base_dn)) {
- $this->_debug("Could not get a valid base dn to search.");
- return null;
- }
-
- $localpart = $username;
-
- if (empty($domain) ) {
- if (count(explode('@', $username)) > 1) {
- $_parts = explode('@', $username);
- $localpart = $_parts[0];
- $domain = $_parts[1];
- }
- else {
- $localpart = $username;
- $domain = '';
- }
- }
-
- $realm = $domain;
- $filter = $this->config_get("login_filter", null);
-
- if (empty($filter)) {
- $filter = $this->config_get("filter", null);
- }
- if (empty($filter)) {
- $filter = "(&(|(mail=%s)(mail=%U@%d)(alias=%s)(alias=%U@%d)(uid=%s))(objectclass=inetorgperson))";
- }
-
- $this->_debug("Net::LDAP3::login() original filter: " . $filter);
-
- $replace_patterns = array(
- '/%s/' => $username,
- '/%d/' => $domain,
- '/%U/' => $localpart,
- '/%r/' => $realm
- );
-
- $filter = preg_replace(array_keys($replace_patterns), array_values($replace_patterns), $filter);
-
- $this->_debug("Net::LDAP3::login() actual filter: " . $filter);
-
- $result = $this->search($base_dn, $filter, 'sub', $attributes);
-
- if (!$result) {
- $this->_debug("Could not search $base_dn with $filter");
- return null;
- }
-
- if ($result->count() > 1) {
- $this->_debug("Multiple entries found.");
- return null;
- }
- else if ($result->count() < 1) {
- $this->_debug("No entries found.");
- return null;
- }
-
- $entries = $result->entries(true);
- $entry_dn = key($entries);
-
- $bound = $this->bind($entry_dn, $password);
-
- if (!$bound) {
- $this->_debug("Could not bind with " . $entry_dn);
- return null;
- }
-
- // replace attributes list with key-value data
- if (!empty($attributes)) {
- $attributes = $entries[$entry_dn];
- }
-
- return $entry_dn;
- }
-
- public function list_group_members($dn, $entry = null, $recurse = true)
- {
- $this->_debug("Net_LDAP3::list_group_members($dn)");
-
- if (is_array($entry) && in_array('objectclass', $entry)) {
- if (!in_array(array('groupofnames', 'groupofuniquenames', 'groupofurls'), $entry['objectclass'])) {
- $this->_debug("Called list_group_members on a non-group!");
- return array();
- }
- }
- else {
- $entry = $this->get_entry($dn, array('member', 'uniquemember', 'memberurl', 'objectclass'));
-
- if (!$entry) {
- return array();
- }
- }
-
- $group_members = array();
-
- foreach ((array)$entry['objectclass'] as $objectclass) {
- switch (strtolower($objectclass)) {
- case "groupofnames":
- case "kolabgroupofnames":
- $group_members = array_merge($group_members, $this->list_group_member($dn, $entry['member'], $recurse));
- break;
- case "groupofuniquenames":
- case "kolabgroupofuniquenames":
- $group_members = array_merge($group_members, $this->list_group_uniquemember($dn, $entry['uniquemember'], $recurse));
- break;
- case "groupofurls":
- $group_members = array_merge($group_members, $this->list_group_memberurl($dn, $entry['memberurl'], $recurse));
- break;
- }
- }
-
- return array_values(array_filter($group_members));
- }
-
- public function modify_entry($subject_dn, $old_attrs, $new_attrs)
- {
- $this->_debug("OLD ATTRIBUTES", $old_attrs);
- $this->_debug("NEW ATTRIBUTES", $new_attrs);
-
- // TODO: Get $rdn_attr - we have type_id in $new_attrs
- $dn_components = ldap_explode_dn($subject_dn, 0);
- $rdn_components = explode('=', $dn_components[0]);
- $rdn_attr = $rdn_components[0];
-
- $this->_debug("Net_LDAP3::modify_entry() using rdn attribute: " . $rdn_attr);
-
- $mod_array = array(
- 'add' => array(), // For use with ldap_mod_add()
- 'del' => array(), // For use with ldap_mod_del()
- 'replace' => array(), // For use with ldap_mod_replace()
- 'rename' => array(), // For use with ldap_rename()
- );
-
- // This is me cheating. Remove this special attribute.
- if (array_key_exists('ou', $old_attrs) || array_key_exists('ou', $new_attrs)) {
- $old_ou = is_array($old_attrs['ou']) ? array_shift($old_attrs['ou']) : $old_attrs['ou'];
- $new_ou = is_array($new_attrs['ou']) ? array_shift($new_attrs['ou']) : $new_attrs['ou'];
- unset($old_attrs['ou']);
- unset($new_attrs['ou']);
- }
- else {
- $old_ou = null;
- $new_ou = null;
- }
-
- // Compare each attribute value of the old attrs with the corresponding value
- // in the new attrs, if any.
- foreach ($old_attrs as $attr => $old_attr_value) {
- if (is_array($old_attr_value)) {
- if (count($old_attr_value) == 1) {
- $old_attrs[$attr] = $old_attr_value[0];
- $old_attr_value = $old_attrs[$attr];
- }
- }
-
- if (array_key_exists($attr, $new_attrs)) {
- if (is_array($new_attrs[$attr])) {
- if (count($new_attrs[$attr]) == 1) {
- $new_attrs[$attr] = $new_attrs[$attr][0];
- }
- }
-
- if (is_array($old_attrs[$attr]) && is_array($new_attrs[$attr])) {
- $_sort1 = $new_attrs[$attr];
- sort($_sort1);
- $_sort2 = $old_attr_value;
- sort($_sort2);
- }
- else {
- $_sort1 = true;
- $_sort2 = false;
- }
-
- if ($new_attrs[$attr] !== $old_attr_value && $_sort1 !== $_sort2) {
- $this->_debug("Attribute $attr changed from " . var_export($old_attr_value, true) . " to " . var_export($new_attrs[$attr], true));
- if ($attr === $rdn_attr) {
- $this->_debug("This attribute is the RDN attribute. Let's see if it is multi-valued, and if the original still exists in the new value.");
- if (is_array($old_attrs[$attr])) {
- if (!is_array($new_attrs[$attr])) {
- if (in_array($new_attrs[$attr], $old_attrs[$attr])) {
- // TODO: Need to remove all $old_attrs[$attr] values not equal to $new_attrs[$attr], and not equal to the current $rdn_attr value [0]
-
- $this->_debug("old attrs. is array, new attrs. is not array. new attr. exists in old attrs.");
-
- $rdn_attr_value = array_shift($old_attrs[$attr]);
- $_attr_to_remove = array();
-
- foreach ($old_attrs[$attr] as $value) {
- if (strtolower($value) != strtolower($new_attrs[$attr])) {
- $_attr_to_remove[] = $value;
- }
- }
-
- $this->_debug("Adding to delete attribute $attr values:" . implode(', ', $_attr_to_remove));
-
- $mod_array['del'][$attr] = $_attr_to_remove;
-
- if (strtolower($new_attrs[$attr]) !== strtolower($rdn_attr_value)) {
- $this->_debug("new attrs is not the same as the old rdn value, issuing a rename");
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
- }
- }
- else {
- $this->_debug("new attrs is not the same as any of the old rdn value, issuing a full rename");
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
- }
- }
- else {
- // TODO: See if the rdn attr. value is still in $new_attrs[$attr]
- if (in_array($old_attrs[$attr][0], $new_attrs[$attr])) {
- $this->_debug("Simply replacing attr $attr as rdn attr value is preserved.");
- $mod_array['replace'][$attr] = $new_attrs[$attr];
- }
- else {
- // TODO: This fails.
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr][0], true);
- $mod_array['del'][$attr] = $old_attrs[$attr][0];
- }
- }
- }
- else {
- if (!is_array($new_attrs[$attr])) {
- $this->_debug("Renaming " . $old_attrs[$attr] . " to " . $new_attrs[$attr]);
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
- }
- else {
- $this->_debug("Adding to replace");
- // An additional attribute value is being supplied. Just replace and continue.
- $mod_array['replace'][$attr] = $new_attrs[$attr];
- continue;
- }
- }
- }
- else {
- if (!isset($new_attrs[$attr]) || $new_attrs[$attr] === '' || (is_array($new_attrs[$attr]) && empty($new_attrs[$attr]))) {
- switch ($attr) {
- case "userpassword":
- break;
- default:
- $this->_debug("Adding to del: $attr");
- $mod_array['del'][$attr] = (array)($old_attr_value);
- break;
- }
- }
- else {
- $this->_debug("Adding to replace: $attr");
- $mod_array['replace'][$attr] = (array)($new_attrs[$attr]);
- }
- }
- }
- else {
- $this->_debug("Attribute $attr unchanged");
- }
- }
- else {
- // TODO: Since we're not shipping the entire object back and forth, and only post
- // part of the data... we don't know what is actually removed (think modifiedtimestamp, etc.)
- $this->_debug("Group attribute $attr not mentioned in \$new_attrs..., but not explicitly removed... by assumption");
- }
- }
-
- foreach ($new_attrs as $attr => $value) {
- // OU's parent base dn
- if ($attr == 'base_dn') {
- continue;
- }
-
- if (is_array($value)) {
- if (count($value) == 1) {
- $new_attrs[$attr] = $value[0];
- $value = $new_attrs[$attr];
- }
- }
-
- if (array_key_exists($attr, $old_attrs)) {
- if (is_array($old_attrs[$attr])) {
- if (count($old_attrs[$attr]) == 1) {
- $old_attrs[$attr] = $old_attrs[$attr][0];
- }
- }
-
- if (is_array($new_attrs[$attr]) && is_array($old_attrs[$attr])) {
- $_sort1 = $old_attrs[$attr];
- sort($_sort1);
- $_sort2 = $value;
- sort($_sort2);
- }
- else {
- $_sort1 = true;
- $_sort2 = false;
- }
-
- if ($value === null || $value === '' || (is_array($value) && empty($value))) {
- if (!array_key_exists($attr, $mod_array['del'])) {
- switch ($attr) {
- case 'userpassword':
- break;
- default:
- $this->_debug("Adding to del(2): $attr");
- $mod_array['del'][$attr] = (array)($old_attrs[$attr]);
- break;
- }
- }
- }
- else {
- if (!($old_attrs[$attr] === $value) && !($attr === $rdn_attr) && !($_sort1 === $_sort2)) {
- if (!array_key_exists($attr, $mod_array['replace'])) {
- $this->_debug("Adding to replace(2): $attr");
- $mod_array['replace'][$attr] = $value;
- }
- }
- }
- }
- else {
- if (!empty($value)) {
- $mod_array['add'][$attr] = $value;
- }
- }
- }
-
- if (empty($old_ou)) {
- $subject_dn_components = ldap_explode_dn($subject_dn, 0);
- unset($subject_dn_components["count"]);
- $subject_rdn = array_shift($subject_dn_components);
- $old_ou = implode(',', $subject_dn_components);
- }
-
- $subject_dn = self::unified_dn($subject_dn);
- $prefix = self::unified_dn('ou=' . $old_ou) . ',';
-
- // object is an organizational unit
- if (strpos($subject_dn, $prefix) === 0) {
- $root = substr($subject_dn, strlen($prefix)); // remove ou=*,
-
- if ((!empty($new_attrs['base_dn']) && strtolower($new_attrs['base_dn']) !== strtolower($root))
- || (strtolower($old_ou) !== strtolower($new_ou))
- ) {
- if (!empty($new_attrs['base_dn'])) {
- $root = $new_attrs['base_dn'];
- }
-
- $mod_array['rename']['new_parent'] = $root;
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = 'ou=' . self::quote_string($new_ou, true);
- }
- }
- // not OU object, but changed ou attribute
- else if (!empty($old_ou) && !empty($new_ou)) {
- // unify DN strings for comparison
- $old_ou = self::unified_dn($old_ou);
- $new_ou = self::unified_dn($new_ou);
-
- if (strtolower($old_ou) !== strtolower($new_ou)) {
- $mod_array['rename']['new_parent'] = $new_ou;
- if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) {
- $rdn_attr_value = self::quote_string($new_attrs[$rdn_attr], true);
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . $rdn_attr_value;
- }
- }
- }
-
- $this->_debug($mod_array);
-
- $result = $this->modify_entry_attributes($subject_dn, $mod_array);
-
- if ($result) {
- return $mod_array;
- }
- }
-
- /**
- * Bind connection with (SASL-) user and password
- *
- * @param string $authc Authentication user
- * @param string $pass Bind password
- * @param string $authz Autorization user
- *
- * @return boolean True on success, False on error
- */
- public function sasl_bind($authc, $pass, $authz=null)
- {
- if (!$this->conn) {
- return false;
- }
-
- if (!function_exists('ldap_sasl_bind')) {
- $this->_error("Unable to bind: ldap_sasl_bind() not exists");
- return false;
- }
-
- if (!empty($authz)) {
- $authz = 'u:' . $authz;
- }
-
- $method = $this->config_get('auth_method');
- if (empty($method)) {
- $method = 'DIGEST-MD5';
- }
-
- $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz]");
-
- if (ldap_sasl_bind($this->conn, null, $pass, $method, null, $authc, $authz)) {
- $this->_debug("S: OK");
- return true;
- }
-
- $this->_debug("S: ".ldap_error($this->conn));
- $this->_error("Bind failed for authcid=$authc ".ldap_error($this->conn));
-
- return false;
- }
-
- /**
- * Execute LDAP search
- *
- * @param string $base_dn Base DN to use for searching
- * @param string $filter Filter string to query
- * @param string $scope The LDAP scope (list|sub|base)
- * @param array $attrs List of entry attributes to read
- * @param array $prop Hash array with query configuration properties:
- * - sort: array of sort attributes (has to be in sync with the VLV index)
- * - search: search string used for VLV controls
- * @param bool $count_only Set to true if only entry count is requested
- *
- * @return mixed Net_LDAP3_Result object or number of entries (if $count_only=true) or False on failure
- */
- public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $props = array(), $count_only = false)
- {
- if (!$this->conn) {
- $this->_debug("No active connection for " . __CLASS__ . "::" . __FUNCTION__);
- return false;
- }
-
- // make sure attributes list is not empty
- if (empty($attrs)) {
- $attrs = array('dn');
- }
- // make sure filter is not empty
- if (empty($filter)) {
- $filter = '(objectclass=*)';
- }
-
- $this->_debug("C: Search base dn: [$base_dn] scope [$scope] with filter [$filter]");
-
- $function = self::scope_to_function($scope, $ns_function);
-
- if (!$count_only && ($sort = $this->find_vlv($base_dn, $filter, $scope, $props['sort']))) {
- // when using VLV, we get the total count by...
- // ...either reading numSubOrdinates attribute
- if (($sub_filter = $this->config_get('numsub_filter')) &&
- ($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, array('numSubOrdinates'), 0, 0, 0))
- ) {
- $counts = ldap_get_entries($this->conn, $result_count);
- for ($vlv_count = $j = 0; $j < $counts['count']; $j++) {
- $vlv_count += $counts[$j]['numsubordinates'][0];
- }
- $this->_debug("D: total numsubordinates = " . $vlv_count);
- }
- // ...or by fetching all records dn and count them
- else if (!function_exists('ldap_parse_virtuallist_control')) {
- // @FIXME: this search will ignore $props['search']
- $vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $props, true);
- }
-
- $this->vlv_active = $this->_vlv_set_controls($sort, $this->list_page, $this->page_size,
- $this->_vlv_search($sort, $props['search']));
- }
- else {
- $this->vlv_active = false;
- }
-
- $sizelimit = (int) $this->config['sizelimit'];
- $timelimit = (int) $this->config['timelimit'];
- $phplimit = (int) @ini_get('max_execution_time');
-
- // set LDAP time limit to be (one second) less than PHP time limit
- // otherwise we have no chance to log the error below
- if ($phplimit && $timelimit >= $phplimit) {
- $timelimit = $phplimit - 1;
- }
-
- $this->_debug("Using function $function on scope $scope (\$ns_function is $ns_function)");
-
- if ($this->vlv_active) {
- if (!empty($this->additional_filter)) {
- $filter = "(&" . $filter . $this->additional_filter . ")";
- $this->_debug("C: (With VLV) Setting a filter (with additional filter) of " . $filter);
- }
- else {
- $this->_debug("C: (With VLV) Setting a filter (without additional filter) of " . $filter);
- }
- }
- else {
- if (!empty($this->additional_filter)) {
- $filter = "(&" . $filter . $this->additional_filter . ")";
- }
- $this->_debug("C: (Without VLV) Setting a filter of " . $filter);
- }
-
- $this->_debug("Executing search with return attributes: " . var_export($attrs, true));
-
- $ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit);
-
- if (!$ldap_result) {
- $this->_warning("$function failed for dn=$base_dn: ".ldap_error($this->conn));
- return false;
- }
-
- // when running on a patched PHP we can use the extended functions
- // to retrieve the total count from the LDAP search result
- if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
- if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) {
- ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult);
- $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$vlv_count");
- }
- else {
- $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
- }
- }
- else {
- $this->_debug("S: ".ldap_count_entries($this->conn, $ldap_result)." record(s) found");
- }
-
- $result = new Net_LDAP3_Result($this->conn, $base_dn, $filter, $scope, $ldap_result);
-
- if (isset($last_offset)) {
- $result->set('offset', $last_offset);
- }
- if (isset($vlv_count)) {
- $result->set('count', $vlv_count);
- }
-
- $result->set('vlv', $this->vlv_active);
-
- return $count_only ? $result->count() : $result;
- }
-
- /**
- * Similar to Net_LDAP3::search() but using a search array with multiple
- * keys and values that to continue to use the VLV but with an original
- * filter adding the search stuff to an additional filter.
- *
- * @see Net_LDAP3::search()
- */
- public function search_entries($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $props = array())
- {
- $this->_debug("Net_LDAP3::search_entries with search " . var_export($props, true));
-
- if (is_array($props['search']) && array_key_exists('params', $props['search'])) {
- $_search = $this->search_filter($props['search']);
- $this->_debug("C: Search filter: $_search");
-
- if (!empty($_search)) {
- $this->additional_filter = $_search;
- }
- else {
- $this->additional_filter = "(|";
-
- foreach ($props['search'] as $attr => $value) {
- $this->additional_filter .= "(" . $attr . "=" . $this->_fuzzy_search_prefix() . $value . $this->_fuzzy_search_suffix() . ")";
- }
-
- $this->additional_filter .= ")";
- }
-
- $this->_debug("C: Setting an additional filter " . $this->additional_filter);
- }
-
- $search = $this->search($base_dn, $filter, $scope, $attrs, $props);
-
- $this->additional_filter = null;
-
- if (!$search) {
- $this->_debug("Net_LDAP3: Search did not succeed!");
- return false;
- }
-
- return $search;
- }
-
- /**
- * Create LDAP search filter string according to defined parameters.
- */
- public function search_filter($search)
- {
- if (empty($search) || !is_array($search) || empty($search['params'])) {
- return null;
- }
-
- $operators = array('=', '~=', '>=', '<=');
- $filter = '';
-
- foreach ((array) $search['params'] as $field => $param) {
- $value = (array) $param['value'];
-
- switch ((string)$param['type']) {
- case 'prefix':
- $prefix = '';
- $suffix = '*';
- break;
-
- case 'suffix':
- $prefix = '*';
- $suffix = '';
- break;
-
- case 'exact':
- case '=':
- case '~=':
- case '>=':
- case '<=':
- $prefix = '';
- $suffix = '';
-
- // this is a common query to find entry by DN, make sure
- // it is a unified DN so special characters are handled correctly
- if ($field == 'entrydn') {
- $value = array_map(array('Net_LDAP3', 'unified_dn'), $value);
- }
-
- break;
-
- case 'exists':
- $prefix = '*';
- $suffix = '';
- $param['value'] = '';
- break;
-
- case 'both':
- default:
- $prefix = '*';
- $suffix = '*';
- break;
- }
-
- $operator = $param['type'] && in_array($param['type'], $operators) ? $param['type'] : '=';
-
- if (count($value) < 2) {
- $value = array_pop($value);
- }
-
- if (is_array($value)) {
- $val_filter = array();
- foreach ($value as $val) {
- $val = self::quote_string($val);
- $val_filter[] = "(" . $field . $operator . $prefix . $val . $suffix . ")";
- }
- $filter .= "(|" . implode($val_filter, '') . ")";
- }
- else {
- $value = self::quote_string($value);
- $filter .= "(" . $field . $operator . $prefix . $value . $suffix . ")";
- }
- }
-
- // join search parameters with specified operator ('OR' or 'AND')
- if (count($search['params']) > 1) {
- $filter = '(' . ($search['operator'] == 'AND' ? '&' : '|') . $filter . ')';
- }
-
- return $filter;
- }
-
- /**
- * Set properties for VLV-based paging
- *
- * @param number $page Page number to list (starting at 1)
- * @param number $size Number of entries to display on one page
- */
- public function set_vlv_page($page, $size = 10)
- {
- $this->list_page = $page;
- $this->page_size = $size;
- }
-
- /**
- * Turn an LDAP entry into a regular PHP array with attributes as keys.
- *
- * @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
- * @param bool $flat Convert one-element-array values into strings
- *
- * @return array Hash array with attributes as keys
- */
- public static function normalize_entry($entry, $flat = false)
- {
- $rec = array();
- for ($i=0; $i < $entry['count']; $i++) {
- $attr = $entry[$i];
- for ($j=0; $j < $entry[$attr]['count']; $j++) {
- $rec[$attr][$j] = $entry[$attr][$j];
- }
-
- if ($flat && count($rec[$attr]) == 1) {
- $rec[$attr] = $rec[$attr][0];
- }
- }
-
- return $rec;
- }
-
- /**
- * Normalize a ldap result by converting entry attribute arrays into single values
- */
- public static function normalize_result($_result)
- {
- if (!is_array($_result)) {
- return array();
- }
-
- $result = array();
-
- for ($x = 0; $x < $_result['count']; $x++) {
- $dn = $_result[$x]['dn'];
- $entry = self::normalize_entry($_result[$x], true);
-
- if (!empty($entry['objectclass'])) {
- if (is_array($entry['objectclass'])) {
- $entry['objectclass'] = array_map('strtolower', $entry['objectclass']);
- }
- else {
- $entry['objectclass'] = strtolower($entry['objectclass']);
- }
- }
-
- $result[$dn] = $entry;
- }
-
- return $result;
- }
-
- public static function scopeint2str($scope)
- {
- switch ($scope) {
- case 2:
- return 'sub';
-
- case 1:
- return 'one';
-
- case 0:
- return 'base';
-
- default:
- $this->_debug("Scope $scope is not a valid scope integer");
- }
- }
-
- /**
- * Choose the right PHP function according to scope property
- *
- * @param string $scope The LDAP scope (sub|base|list)
- * @param string $ns_function Function to be used for numSubOrdinates queries
- * @return string PHP function to be used to query directory
- */
- public static function scope_to_function($scope, &$ns_function = null)
- {
- switch ($scope) {
- case 'sub':
- $function = $ns_function = 'ldap_search';
- break;
- case 'base':
- $function = $ns_function = 'ldap_read';
- break;
- case 'one':
- case 'list':
- default:
- $function = 'ldap_list';
- $ns_function = 'ldap_read';
- break;
- }
-
- return $function;
- }
-
- private function config_set_config_get_hook($callback)
- {
- $this->_config_get_hook = $callback;
- }
-
- private function config_set_config_set_hook($callback)
- {
- $this->_config_set_hook = $callback;
- }
-
- /**
- * Sets the debug level both for this class and the ldap connection.
- */
- private function config_set_debug($value)
- {
- $this->config['debug'] = (bool) $value;
- if ((int)($value) > 0) {
- ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, (int)($value));
- }
- }
-
- /**
- * Sets a log hook that is called with every log message in this module.
- */
- private function config_set_log_hook($callback)
- {
- $this->_log_hook = $callback;
- }
-
- /**
- * Find a matching VLV
- */
- protected function find_vlv($base_dn, $filter, $scope, $sort_attrs = null)
- {
- if ($scope == 'base') {
- return false;
- }
-
- $vlv_indexes = $this->find_vlv_indexes_and_searches();
-
- if (empty($vlv_indexes)) {
- return false;
- }
-
- $this->_debug("Existing vlv index and search information", $vlv_indexes);
-
- $filter = strtolower($filter);
-
- foreach ($vlv_indexes as $vlv_index) {
- if (!empty($vlv_index[$base_dn])) {
- $this->_debug("Found a VLV for base_dn: " . $base_dn);
- if ($vlv_index[$base_dn]['filter'] == $filter) {
- if ($vlv_index[$base_dn]['scope'] == $scope) {
- $this->_debug("Scope and filter matches");
-
- // Not passing any sort attributes means you don't care
- if (!empty($sort_attrs)) {
- $sort_attrs = array_map('strtolower', (array) $sort_attrs);
-
- foreach ($vlv_index[$base_dn]['sort'] as $sss_config) {
- $sss_config = array_map('strtolower', $sss_config);
- if (count(array_intersect($sort_attrs, $sss_config)) == count($sort_attrs)) {
- $this->_debug("Sorting matches");
-
- return $sort_attrs;
- }
- }
-
- $this->_debug("Sorting does not match");
- }
- else {
- $sort = array_filter((array) $vlv_index[$base_dn]['sort'][0]);
- $this->_debug("Sorting unimportant");
-
- return $sort;
- }
- }
- else {
- $this->_debug("Scope does not match");
- }
- }
- else {
- $this->_debug("Filter does not match");
- }
- }
- }
-
- return false;
- }
-
- /**
- * Return VLV indexes and searches including necessary configuration
- * details.
- */
- protected function find_vlv_indexes_and_searches()
- {
- // Use of Virtual List View control has been specifically disabled.
- if ($this->config['vlv'] === false) {
- return false;
- }
-
- // Virtual List View control has been configured in kolab.conf, for example;
- //
- // [ldap]
- // vlv = [
- // {
- // 'ou=People,dc=example,dc=org': {
- // 'scope': 'sub',
- // 'filter': '(objectclass=inetorgperson)',
- // 'sort' : [
- // [
- // 'displayname',
- // 'sn',
- // 'givenname',
- // 'cn'
- // ]
- // ]
- // }
- // },
- // {
- // 'ou=Groups,dc=example,dc=org': {
- // 'scope': 'sub',
- // 'filter': '(objectclass=groupofuniquenames)',
- // 'sort' : [
- // [
- // 'cn'
- // ]
- // ]
- // }
- // },
- // ]
- //
- if (is_array($this->config['vlv'])) {
- return $this->config['vlv'];
- }
-
- // We have done this dance before.
- if ($this->_vlv_indexes_and_searches !== null) {
- return $this->_vlv_indexes_and_searches;
- }
-
- $this->_vlv_indexes_and_searches = array();
-
- $config_root_dn = $this->config_get('config_root_dn');
-
- if (empty($config_root_dn)) {
- return array();
- }
-
- if ($cached_config = $this->get_cache_data('vlvconfig')) {
- $this->_vlv_indexes_and_searches = $cached_config;
- return $this->_vlv_indexes_and_searches;
- }
-
- $this->_debug("No VLV information available yet, refreshing");
-
- $search_filter = '(objectclass=vlvsearch)';
- $search_result = ldap_search($this->conn, $config_root_dn, $search_filter, array('*'), 0, 0, 0);
-
- if ($search_result === false) {
- $this->_debug("Search for '$search_filter' on '$config_root_dn' failed:".ldap_error($this->conn));
- return;
- }
-
- $vlv_searches = new Net_LDAP3_Result($this->conn, $config_root_dn, $search_filter, 'sub', $search_result);
-
- if ($vlv_searches->count() < 1) {
- $this->_debug("Empty result from search for '(objectclass=vlvsearch)' on '$config_root_dn'");
- return;
- }
-
- $index_filter = '(objectclass=vlvindex)';
-
- foreach ($vlv_searches->entries(true) as $vlv_search_dn => $vlv_search_attrs) {
- // The attributes we are interested in are as follows:
- $_vlv_base_dn = $vlv_search_attrs['vlvbase'];
- $_vlv_scope = $vlv_search_attrs['vlvscope'];
- $_vlv_filter = $vlv_search_attrs['vlvfilter'];
-
- // Multiple indexes may exist
- $index_result = ldap_search($this->conn, $vlv_search_dn, $index_filter, array('*'), 0, 0, 0);
-
- if ($index_result === false) {
- $this->_debug("Search for '$index_filter' on '$vlv_search_dn' failed:".ldap_error($this->conn));
- continue;
- }
-
- $vlv_indexes = new Net_LDAP3_Result($this->conn, $vlv_search_dn, $index_filter, 'sub', $index_result);
- $vlv_indexes = $vlv_indexes->entries(true);
-
- // Reset this one for each VLV search.
- $_vlv_sort = array();
-
- foreach ($vlv_indexes as $vlv_index_dn => $vlv_index_attrs) {
- $_vlv_sort[] = explode(' ', trim($vlv_index_attrs['vlvsort']));
- }
-
- $this->_vlv_indexes_and_searches[] = array(
- $_vlv_base_dn => array(
- 'scope' => self::scopeint2str($_vlv_scope),
- 'filter' => strtolower($_vlv_filter),
- 'sort' => $_vlv_sort,
- ),
- );
- }
-
- // cache this
- $this->set_cache_data('vlvconfig', $this->_vlv_indexes_and_searches);
-
- return $this->_vlv_indexes_and_searches;
- }
-
- private function init_schema()
- {
- // use PEAR include if autoloading failed
- if (!class_exists('Net_LDAP2')) {
- require_once('Net/LDAP2.php');
- }
-
- $port = $this->config_get('port', 389);
- $tls = $this->config_get('use_tls', false);
-
- foreach ((array) $this->config_get('hosts') as $host) {
- $this->_debug("C: Connect [$host:$port]");
-
- $_ldap_cfg = array(
- 'host' => $host,
- 'port' => $port,
- 'tls' => $tls,
- 'version' => 3,
- 'binddn' => $this->config_get('service_bind_dn'),
- 'bindpw' => $this->config_get('service_bind_pw')
- );
-
- $_ldap_schema_cache_cfg = array(
- 'path' => "/tmp/" . $host . ":" . ($port ? $port : '389') . "-Net_LDAP2_Schema.cache",
- 'max_age' => 86400,
- );
-
- $_ldap = Net_LDAP2::connect($_ldap_cfg);
-
- if (!is_a($_ldap, 'Net_LDAP2_Error')) {
- $this->_debug("S: OK");
- break;
- }
-
- $this->_debug("S: NOT OK");
- $this->_debug($_ldap->getMessage());
- }
-
- if (is_a($_ldap, 'Net_LDAP2_Error')) {
- return null;
- }
-
- $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);
-
- $_ldap->registerSchemaCache($_ldap_schema_cache);
-
- // TODO: We should learn what LDAP tech. we're running against.
- // Perhaps with a scope base objectclass recognize rootdse entry
- $schema_root_dn = $this->config_get('schema_root_dn');
-
- if (!$schema_root_dn) {
- $_schema = $_ldap->schema();
- }
-
- return $_schema;
- }
-
- private function list_group_member($dn, $members, $recurse = true)
- {
- $this->_debug("Net_LDAP3::list_group_member($dn)");
-
- $members = (array) $members;
- $group_members = array();
-
- // remove possible 'count' item
- unset($members['count']);
-
- // Use the member attributes to return an array of member ldap objects
- // NOTE that the member attribute is supposed to contain a DN
- foreach ($members as $member) {
- $member_entry = $this->get_entry($member, array('member', 'uniquemember', 'memberurl', 'objectclass'));
-
- if (empty($member_entry)) {
- continue;
- }
-
- $group_members[$member] = $member;
-
- if ($recurse) {
- // Nested groups
- $group_group_members = $this->list_group_members($member, $member_entry);
- if ($group_group_members) {
- $group_members = array_merge($group_group_members, $group_members);
- }
- }
- }
-
- return array_filter($group_members);
- }
-
- private function list_group_uniquemember($dn, $uniquemembers, $recurse = true)
- {
- $this->_debug("Net_LDAP3::list_group_uniquemember($dn)", $entry);
-
- $uniquemembers = (array)($uniquemembers);
- $group_members = array();
-
- // remove possible 'count' item
- unset($uniquemembers['count']);
-
- foreach ($uniquemembers as $member) {
- $member_entry = $this->get_entry($member, array('member', 'uniquemember', 'memberurl', 'objectclass'));
-
- if (empty($member_entry)) {
- continue;
- }
-
- $group_members[$member] = $member;
-
- if ($recurse) {
- // Nested groups
- $group_group_members = $this->list_group_members($member, $member_entry);
- if ($group_group_members) {
- $group_members = array_merge($group_group_members, $group_members);
- }
- }
- }
-
- return array_filter($group_members);
- }
-
- private function list_group_memberurl($dn, $memberurls, $recurse = true)
- {
- $this->_debug("Net_LDAP3::list_group_memberurl($dn)");
-
- $group_members = array();
- $memberurls = (array) $memberurls;
- $attributes = array('member', 'uniquemember', 'memberurl', 'objectclass');
-
- // remove possible 'count' item
- unset($memberurls['count']);
-
- foreach ($memberurls as $url) {
- $ldap_uri = $this->parse_memberurl($url);
- $result = $this->search($ldap_uri[3], $ldap_uri[6], 'sub', $attributes);
-
- if (!$result) {
- continue;
- }
-
- foreach ($result->entries(true) as $entry_dn => $_entry) {
- $group_members[$entry_dn] = $entry_dn;
- $this->_debug("Found " . $entry_dn);
-
- if ($recurse) {
- // Nested group
- $group_group_members = $this->list_group_members($entry_dn, $_entry);
- if ($group_group_members) {
- $group_members = array_merge($group_members, $group_group_members);
- }
- }
- }
- }
-
- return array_filter($group_members);
- }
-
- /**
- * memberUrl attribute parser
- *
- * @param string $url URL string
- *
- * @return array URL elements
- */
- private function parse_memberurl($url)
- {
- preg_match('/(.*):\/\/(.*)\/(.*)\?(.*)\?(.*)\?(.*)/', $url, $matches);
- return $matches;
- }
-
- private function modify_entry_attributes($subject_dn, $attributes)
- {
- if (is_array($attributes['rename']) && !empty($attributes['rename'])) {
- $olddn = $attributes['rename']['dn'];
- $newrdn = $attributes['rename']['new_rdn'];
- $new_parent = $attributes['rename']['new_parent'];
-
- $this->_debug("LDAP: C: Rename $olddn to $newrdn,$new_parent");
-
- // Note: for some reason the operation fails if RDN contains special characters
- // and last argument of ldap_rename() is set to TRUE. That's why we use FALSE.
- // However, we need to modify RDN attribute value later, otherwise it
- // will contain an array of previous and current values
- for ($i = 1; $i >= 0; $i--) {
- $result = ldap_rename($this->conn, $olddn, $newrdn, $new_parent, $i == 1);
- if ($result) {
- break;
- }
- }
-
- if ($result) {
- $this->_debug("LDAP: S: OK");
-
- if ($new_parent) {
- $subject_dn = $newrdn . ',' . $new_parent;
- }
- else {
- $old_parent_dn_components = ldap_explode_dn($olddn, 0);
- unset($old_parent_dn_components["count"]);
- $old_rdn = array_shift($old_parent_dn_components);
- $old_parent_dn = implode(",", $old_parent_dn_components);
- $subject_dn = $newrdn . ',' . $old_parent_dn;
- }
-
- // modify RDN attribute value, see note above
- if (!$i && empty($attributes['replace'][$attr])) {
- list($attr, $val) = explode('=', $newrdn, 2);
- $attributes['replace'][$attr] = self::quote_string($val, true, true);
- }
- }
- else {
- $this->_debug("LDAP: S: " . ldap_error($this->conn));
- $this->_warning("LDAP: Failed to rename $olddn to $newrdn,$new_parent");
- return false;
- }
- }
-
- if (is_array($attributes['replace']) && !empty($attributes['replace'])) {
- $this->_debug("LDAP: C: Mod-Replace $subject_dn: " . json_encode($attributes['replace']));
-
- $result = ldap_mod_replace($this->conn, $subject_dn, $attributes['replace']);
-
- if ($result) {
- $this->_debug("LDAP: S: OK");
- }
- else {
- $this->_debug("LDAP: S: " . ldap_error($this->conn));
- $this->_warning("LDAP: Failed to replace attributes on $subject_dn: " . json_encode($attributes['replace']));
- return false;
- }
- }
-
- if (is_array($attributes['del']) && !empty($attributes['del'])) {
- $this->_debug("LDAP: C: Mod-Delete $subject_dn: " . json_encode($attributes['del']));
-
- $result = ldap_mod_del($this->conn, $subject_dn, $attributes['del']);
-
- if ($result) {
- $this->_debug("LDAP: S: OK");
- }
- else {
- $this->_debug("LDAP: S: " . ldap_error($this->conn));
- $this->_warning("LDAP: Failed to delete attributes on $subject_dn: " . json_encode($attributes['del']));
- return false;
- }
- }
-
- if (is_array($attributes['add']) && !empty($attributes['add'])) {
- $this->_debug("LDAP: C: Mod-Add $subject_dn: " . json_encode($attributes['add']));
-
- $result = ldap_mod_add($this->conn, $subject_dn, $attributes['add']);
-
- if ($result) {
- $this->_debug("LDAP: S: OK");
- }
- else {
- $this->_debug("LDAP: S: " . ldap_error($this->conn));
- $this->_warning("LDAP: Failed to add attributes on $subject_dn: " . json_encode($attributes['add']));
- return false;
- }
- }
-
- return true;
- }
-
- private function parse_aclrights(&$attributes, $attribute_value)
- {
- $components = explode(':', $attribute_value);
- $_acl_target = array_shift($components);
- $_acl_value = trim(implode(':', $components));
-
- $_acl_components = explode(';', $_acl_target);
-
- switch ($_acl_components[1]) {
- case "entryLevel":
- $attributes['entryLevelRights'] = Array();
- $_acl_value = explode(',', $_acl_value);
-
- foreach ($_acl_value as $right) {
- list($method, $bool) = explode(':', $right);
- if ($bool == "1" && !in_array($method, $attributes['entryLevelRights'])) {
- $attributes['entryLevelRights'][] = $method;
- }
- }
-
- break;
-
- case "attributeLevel":
- $attributes['attributeLevelRights'][$_acl_components[2]] = Array();
- $_acl_value = explode(',', $_acl_value);
-
- foreach ($_acl_value as $right) {
- list($method, $bool) = explode(':', $right);
- if ($bool == "1" && !in_array($method, $attributes['attributeLevelRights'][$_acl_components[2]])) {
- $attributes['attributeLevelRights'][$_acl_components[2]][] = $method;
- }
- }
-
- break;
-
- default:
- break;
- }
- }
-
- private function parse_attribute_level_rights($attribute_value)
- {
- $attribute_value = str_replace(", ", ",", $attribute_value);
- $attribute_values = explode(",", $attribute_value);
- $attribute_value = array();
-
- foreach ($attribute_values as $access_right) {
- $access_right_components = explode(":", $access_right);
- $access_attribute = strtolower(array_shift($access_right_components));
- $access_value = array_shift($access_right_components);
-
- $attribute_value[$access_attribute] = array();
-
- for ($i = 0; $i < strlen($access_value); $i++) {
- $method = $this->attribute_level_rights_map[substr($access_value, $i, 1)];
-
- if (!in_array($method, $attribute_value[$access_attribute])) {
- $attribute_value[$access_attribute][] = $method;
- }
- }
- }
-
- return $attribute_value;
- }
-
- private function parse_entry_level_rights($attribute_value)
- {
- $_attribute_value = array();
-
- for ($i = 0; $i < strlen($attribute_value); $i++) {
- $method = $this->entry_level_rights_map[substr($attribute_value, $i, 1)];
-
- if (!in_array($method, $_attribute_value)) {
- $_attribute_value[] = $method;
- }
- }
-
- return $_attribute_value;
- }
-
- private function supported_controls()
- {
- if (!empty($this->supported_controls)) {
- return $this->supported_controls;
- }
-
- $this->_info("Obtaining supported controls");
-
- if ($result = $this->search('', '(objectclass=*)', 'base', array('supportedcontrol'))) {
- $result = $result->entries(true);
- $control = $result['']['supportedcontrol'];
- }
- else {
- $control = array();
- }
-
- $this->_info("Obtained " . count($control) . " supported controls");
- $this->supported_controls = $control;
-
- return $control;
- }
-
- private function vendor_name()
- {
- if (!empty($this->vendor_name)) {
- return $this->vendor_name;
- }
-
- $this->_info("Obtaining LDAP server vendor name");
-
- if ($result = $this->search('', '(objectclass=*)', 'base', array('vendorname'))) {
- $result = $result->entries(true);
- $name = $result['']['vendorname'];
- }
- else {
- $name = false;
- }
-
- if ($name !== false) {
- $this->_info("Vendor name is $name");
- } else {
- $this->_info("No vendor name!");
- }
-
- $this->vendor = $name;
-
- return $name;
- }
-
- protected function _alert()
- {
- $this->__log(LOG_ALERT, func_get_args());
- }
-
- protected function _critical()
- {
- $this->__log(LOG_CRIT, func_get_args());
- }
-
- protected function _debug()
- {
- $this->__log(LOG_DEBUG, func_get_args());
- }
-
- protected function _emergency()
- {
- $this->__log(LOG_EMERG, func_get_args());
- }
-
- protected function _error()
- {
- $this->__log(LOG_ERR, func_get_args());
- }
-
- protected function _info()
- {
- $this->__log(LOG_INFO, func_get_args());
- }
-
- protected function _notice()
- {
- $this->__log(LOG_NOTICE, func_get_args());
- }
-
- protected function _warning()
- {
- $this->__log(LOG_WARNING, func_get_args());
- }
-
- /**
- * Log a message.
- */
- private function __log($level, $args)
- {
- $msg = array();
-
- foreach ($args as $arg) {
- $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
- }
-
- if (!empty($this->_log_hook)) {
- call_user_func_array($this->_log_hook, array($level, $msg));
- return;
- }
-
- if ($this->debug_level > 0) {
- syslog($level, implode("\n", $msg));
- }
- }
-
- /**
- * Add BER sequence with correct length and the given identifier
- */
- private static function _ber_addseq($str, $identifier)
- {
- $len = dechex(strlen($str)/2);
- if (strlen($len) % 2 != 0) {
- $len = '0'.$len;
- }
-
- return $identifier . $len . $str;
- }
-
- /**
- * Returns BER encoded integer value in hex format
- */
- private static function _ber_encode_int($offset)
- {
- $val = dechex($offset);
- $prefix = '';
-
- // check if bit 8 of high byte is 1
- if (preg_match('/^[89abcdef]/', $val)) {
- $prefix = '00';
- }
-
- if (strlen($val)%2 != 0) {
- $prefix .= '0';
- }
-
- return $prefix . $val;
- }
-
- /**
- * Quotes attribute value string
- *
- * @param string $str Attribute value
- * @param bool $dn True if the attribute is a DN
- * @param bool $reverse Do reverse replacement
- *
- * @return string Quoted string
- */
- public static function quote_string($str, $is_dn = false, $reverse = false)
- {
- // take first entry if array given
- if (is_array($str)) {
- $str = reset($str);
- }
-
- if ($is_dn) {
- $replace = array(
- ',' => '\2c',
- '=' => '\3d',
- '+' => '\2b',
- '<' => '\3c',
- '>' => '\3e',
- ';' => '\3b',
- "\\"=> '\5c',
- '"' => '\22',
- '#' => '\23'
- );
- }
- else {
- $replace = array(
- '*' => '\2a',
- '(' => '\28',
- ')' => '\29',
- "\\" => '\5c',
- '/' => '\2f'
- );
- }
-
- if ($reverse) {
- return str_replace(array_values($replace), array_keys($replace), $str);
- }
-
- return strtr($str, $replace);
- }
-
- /**
- * Unify DN string for comparison
- *
- * @para string $str DN string
- *
- * @return string Unified DN string
- */
- public static function unified_dn($str)
- {
- $result = array();
-
- foreach (explode(',', $str) as $token) {
- list($attr, $value) = explode('=', $token, 2);
-
- $pos = 0;
- while (preg_match('/\\\\[0-9a-fA-F]{2}/', $value, $matches, PREG_OFFSET_CAPTURE, $pos)) {
- $char = chr(hexdec(substr($matches[0][0], 1)));
- $pos = $matches[0][1];
- $value = substr_replace($value, $char, $pos, 3);
- $pos += 1;
- }
-
- $result[] = $attr . '=' . self::quote_string($value, true);
- }
-
- return implode(',', $result);
- }
-
- /**
- * create ber encoding for sort control
- *
- * @param array List of cols to sort by
- * @return string BER encoded option value
- */
- private static function _sort_ber_encode($sortcols)
- {
- $str = '';
- foreach (array_reverse((array)$sortcols) as $col) {
- $ber_val = self::_string2hex($col);
-
- // 30 = ber sequence with a length of octet value
- // 04 = octet string with a length of the ascii value
- $oct = self::_ber_addseq($ber_val, '04');
- $str = self::_ber_addseq($oct, '30') . $str;
- }
-
- // now tack on sequence identifier and length
- $str = self::_ber_addseq($str, '30');
-
- return pack('H'.strlen($str), $str);
- }
-
- /**
- * Returns ascii string encoded in hex
- */
- private static function _string2hex($str)
- {
- $hex = '';
- for ($i=0; $i < strlen($str); $i++)
- $hex .= dechex(ord($str[$i]));
-
- return $hex;
- }
-
- /**
- * Generate BER encoded string for Virtual List View option
- *
- * @param integer List offset (first record)
- * @param integer Records per page
- * @return string BER encoded option value
- */
- private static function _vlv_ber_encode($offset, $rpp, $search = '')
- {
- // This string is ber-encoded, php will prefix this value with:
- // 04 (octet string) and 10 (length of 16 bytes)
- // the code behind this string is broken down as follows:
- // 30 = ber sequence with a length of 0e (14) bytes following
- // 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
- // 02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24)
- // a0 = type context-specific/constructed with a length of 06 (6) bytes following
- // 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
- // 02 = type integer with 2 bytes following (contentCount): 01 00
-
- // whith a search string present:
- // 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
- // 81 indicates a user string is present where as a a0 indicates just a offset search
- // 81 = type context-specific/constructed with a length of 06 (6) bytes following
-
- // the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
- // encoding of integer values (note: these values are in
- // two-complement form so since offset will never be negative bit 8 of the
- // leftmost octet should never by set to 1):
- // 8.3.2: If the contents octets of an integer value encoding consist
- // of more than one octet, then the bits of the first octet (rightmost) and bit 8
- // of the second (to the left of first octet) octet:
- // a) shall not all be ones; and
- // b) shall not all be zero
-
- if ($search) {
- $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
- $ber_val = self::_string2hex($search);
- $str = self::_ber_addseq($ber_val, '81');
- }
- else {
- // construct the string from right to left
- $str = "020100"; # contentCount
-
- // returns encoded integer value in hex format
- $ber_val = self::_ber_encode_int($offset);
-
- // calculate octet length of $ber_val
- $str = self::_ber_addseq($ber_val, '02') . $str;
-
- // now compute length over $str
- $str = self::_ber_addseq($str, 'a0');
- }
-
- // now tack on records per page
- $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
-
- // now tack on sequence identifier and length
- $str = self::_ber_addseq($str, '30');
-
- return pack('H'.strlen($str), $str);
- }
-
- private function _fuzzy_search_prefix()
- {
- switch ($this->config_get("fuzzy_search", 2)) {
- case 2:
- return "*";
- break;
- case 1:
- case 0:
- default:
- return "";
- break;
- }
- }
-
- private function _fuzzy_search_suffix()
- {
- switch ($this->config_get("fuzzy_search", 2)) {
- case 2:
- return "*";
- break;
- case 1:
- return "*";
- case 0:
- default:
- return "";
- break;
- }
- }
-
- /**
- * Return the search string value to be used in VLV controls
- *
- * @param array $sort List of attributes in vlv index
- * @param array|string $search Search string or attribute => value hash
- *
- * @return string Search string
- */
- private function _vlv_search($sort, $search)
- {
- if (!empty($this->additional_filter)) {
- $this->_debug("Not setting a VLV search filter because we already have a filter");
- return;
- }
-
- if (empty($search)) {
- return;
- }
-
- foreach ((array) $search as $attr => $value) {
- if ($attr && !in_array(strtolower($attr), $sort)) {
- $this->_debug("Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
- return;
- }
- else {
- return $value . $this->_fuzzy_search_suffix();
- }
- }
- }
-
- /**
- * Set server controls for Virtual List View (paginated listing)
- */
- private function _vlv_set_controls($sort, $list_page, $page_size, $search = null)
- {
- $sort_ctrl = array(
- 'oid' => "1.2.840.113556.1.4.473",
- 'value' => self::_sort_ber_encode($sort)
- );
-
- if (!empty($search)) {
- $this->_debug("_vlv_set_controls to include search: " . var_export($search, true));
- }
-
- $vlv_ctrl = array(
- 'oid' => "2.16.840.1.113730.3.4.9",
- 'value' => self::_vlv_ber_encode(
- $offset = ($list_page-1) * $page_size + 1,
- $page_size,
- $search
- ),
- 'iscritical' => true
- );
-
- $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value']))
- . " (" . implode(',', (array) $sort) . ");"
- . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");
-
- if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) {
- $this->_debug("S: ".ldap_error($this->conn));
- $this->set_error(self::ERROR_SEARCH, 'vlvnotsupported');
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Get global handle for cache access
- *
- * @return object Cache object
- */
- public function get_cache()
- {
- if ($this->cache === true) {
- // no memcache support in PHP
- if (!class_exists('Memcache')) {
- $this->cache = false;
- return false;
- }
-
- // add all configured hosts to pool
- $pconnect = $this->config_get('memcache_pconnect');
- $hosts = $this->config_get('memcache_hosts');
-
- if ($hosts) {
- $this->cache = new Memcache;
- $this->mc_available = 0;
-
- $hosts = explode(',', $hosts);
- foreach ($hosts as $host) {
- $host = trim($host);
- if (substr($host, 0, 7) != 'unix://') {
- list($host, $port) = explode(':', $host);
- if (!$port) $port = 11211;
- }
- else {
- $port = 0;
- }
-
- $this->mc_available += intval($this->cache->addServer(
- $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
- }
-
- // test connection and failover (will result in $this->mc_available == 0 on complete failure)
- $this->cache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
- }
-
- if (!$this->mc_available) {
- $this->cache = false;
- }
- }
-
- return $this->cache;
- }
-
- /**
- * Callback for memcache failure
- */
- public function memcache_failure($host, $port)
- {
- static $seen = array();
-
- // only report once
- if (!$seen["$host:$port"]++) {
- $this->mc_available--;
- $this->_error("Memcache failure on host $host:$port");
- }
- }
-
- /**
- * Get cached data
- *
- * @param string $key Cache key
- *
- * @return mixed Cached value
- */
- public function get_cache_data($key)
- {
- if ($cache = $this->get_cache()) {
- return $cache->get($key);
- }
- }
-
- /**
- * Store cached data
- *
- * @param string $key Cache key
- * @param mixed $data Data
- * @param int $ttl Cache TTL in seconds
- *
- * @return bool False on failure or when cache is disabled, True if data was saved succesfully
- */
- public function set_cache_data($key, $data, $ttl = 3600)
- {
- if ($cache = $this->get_cache()) {
- if (!method_exists($cache, 'replace') || !$cache->replace($key, $data, MEMCACHE_COMPRESSED, $ttl)) {
- return $cache->set($key, $data, MEMCACHE_COMPRESSED, $ttl);
- }
- else {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Translate a domain name into it's corresponding root dn.
- *
- * @param string $domain Domain name
- *
- * @return string|bool Domain root DN or False on error
- */
- public function domain_root_dn($domain)
- {
- if (empty($domain)) {
- return false;
- }
-
- $ckey = 'domain.root::' . $domain;
- if ($result = $this->icache[$ckey]) {
- return $result;
- }
-
- $this->_debug("Net_LDAP3::domain_root_dn($domain)");
-
- if ($entry_attrs = $this->find_domain($domain)) {
- $name_attribute = $this->config_get('domain_name_attribute');
-
- if (empty($name_attribute)) {
- $name_attribute = 'associateddomain';
- }
-
- if (is_array($entry_attrs)) {
- if (!empty($entry_attrs['inetdomainbasedn'])) {
- $domain_root_dn = $entry_attrs['inetdomainbasedn'];
- }
- else {
- if (is_array($entry_attrs[$name_attribute])) {
- $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute][0]);
- }
- else {
- $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute]);
- }
- }
- }
- }
-
- if (empty($domain_root_dn)) {
- $domain_root_dn = $this->_standard_root_dn($domain);
- }
-
- $this->_debug("Net_LDAP3::domain_root_dn() result: $domain_root_dn");
-
- return $this->icache[$ckey] = $domain_root_dn;
- }
-
- /**
- * Find domain by name
- *
- * @param string $domain Domain name
- * @param array $attributes Result attributes
- *
- * @return array|bool Domain attributes (plus 'dn' attribute) or False if not found
- */
- public function find_domain($domain, $attributes = array('*'))
- {
- if (empty($domain)) {
- return false;
- }
-
- $ckey = 'domain::' . $domain;
- $ickey = $ckey . '::' . md5(implode(',', $attributes));
-
- if (isset($this->icache[$ickey])) {
- return $this->icache[$ickey];
- }
-
- $this->_debug("Net_LDAP3::find_domain($domain)");
-
- // use cache
- $domain_dn = $this->get_cache_data($ckey);
-
- if ($domain_dn) {
- $result = $this->get_entry_attributes($domain_dn, $attributes);
- if (!empty($result)) {
- $result['dn'] = $domain_dn;
- }
- else {
- $result = false;
- }
- }
- else {
- $domain_base_dn = $this->config_get('domain_base_dn');
- $domain_filter = $this->config_get('domain_filter');
- $name_attribute = $this->config_get('domain_name_attribute');
-
- if (empty($name_attribute)) {
- $name_attribute = 'associateddomain';
- }
-
- $domain_filter = "(&" . $domain_filter . "(" . $name_attribute . "=" . self::quote_string($domain) . "))";
-
- if ($result = $this->search($domain_base_dn, $domain_filter, 'sub', $attributes)) {
- $result = $result->entries(true);
- $domain_dn = key($result);
- $result = $result[$domain_dn];
- $result['dn'] = $domain_dn;
-
- // cache domain DN
- $this->set_cache_data($ckey, $domain_dn);
- }
- }
-
- $this->_debug("Net_LDAP3::find_domain() result: " . var_export($result, true));
-
- return $this->icache[$ickey] = $result;
- }
-
- /**
- * From a domain name, such as 'kanarip.com', create a standard root
- * dn, such as 'dc=kanarip,dc=com'.
- *
- * As the parameter $associatedDomains, either pass it an array (such
- * as may have been returned by ldap_get_entries() or perhaps
- * ldap_list()), where the function will assume the first value
- * ($array[0]) to be the uber-level domain name, or pass it a string
- * such as 'kanarip.nl'.
- *
- * @return string
- */
- protected function _standard_root_dn($associatedDomains)
- {
- if (is_array($associatedDomains)) {
- // Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
- if ($associatedDomains['count'] > 1) {
- // Issue a debug message here
- $relevant_associatedDomain = $associatedDomains[0];
- }
- else {
- $relevant_associatedDomain = $associatedDomains[0];
- }
- }
- else {
- $relevant_associatedDomain = $associatedDomains;
- }
-
- return 'dc=' . implode(',dc=', explode('.', $relevant_associatedDomain));
- }
-}
diff --git a/lib/ext/Net/LDAP3/Result.php b/lib/ext/Net/LDAP3/Result.php
deleted file mode 100644
index b89c9fd..0000000
--- a/lib/ext/Net/LDAP3/Result.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | Net/LDAP3/Result.php |
- | |
- | Based on code created by the Roundcube Webmail team. |
- | |
- | Copyright (C) 2006-2014, The Roundcube Dev Team |
- | Copyright (C) 2012-2014, Kolab Systems AG |
- | |
- | This program is free software: you can redistribute it and/or modify |
- | it under the terms of the GNU General Public License as published by |
- | the Free Software Foundation, either version 3 of the License, or |
- | (at your option) any later version. |
- | |
- | This program is distributed in the hope that it will be useful, |
- | but WITHOUT ANY WARRANTY; without even the implied warranty of |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
- | GNU General Public License for more details. |
- | |
- | You should have received a copy of the GNU General Public License |
- | along with this program. If not, see <http://www.gnu.org/licenses/>. |
- | |
- | PURPOSE: |
- | Provide advanced functionality for accessing LDAP directories |
- | |
- +-----------------------------------------------------------------------+
- | Authors: Thomas Bruederli <roundcube@gmail.com> |
- | Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
- +-----------------------------------------------------------------------+
-*/
-
-/**
- * Model class representing an LDAP search result
- *
- * @package LDAP
- */
-class Net_LDAP3_Result implements Iterator
-{
- protected $conn;
- protected $base_dn;
- protected $filter;
- protected $scope;
-
- private $count;
- private $current;
- private $iteratorkey = 0;
-
- /**
- * Default constructor
- *
- * @param resource $conn LDAP link identifier
- * @param string $base_dn Base DN used to get this result
- * @param string $filter Filter query used to get this result
- * @param string $scope Scope of the result
- * @param resource $result LDAP result entry identifier
- */
- function __construct($conn, $base_dn, $filter, $scope, $result)
- {
- $this->conn = $conn;
- $this->base_dn = $base_dn;
- $this->filter = $filter;
- $this->scope = $scope;
- $this->result = $result;
- }
-
- public function get($property, $default = null)
- {
- if (isset($this->$property)) {
- return $this->$property;
- } else {
- return $default;
- }
- }
-
- public function set($property, $value)
- {
- $this->$property = $value;
- }
-
- /**
- * Wrapper for ldap_sort()
- */
- public function sort($attr)
- {
- return ldap_sort($this->conn, $this->result, $attr);
- }
-
- /**
- * Get entries count
- */
- public function count()
- {
- if (!isset($this->count)) {
- $this->count = ldap_count_entries($this->conn, $this->result);
- }
-
- return $this->count;
- }
-
- /**
- * Wrapper for ldap_get_entries()
- *
- * @param bool $normalize Optionally normalize the entries to a list of hash arrays
- *
- * @return array List of LDAP entries
- */
- public function entries($normalize = false)
- {
- $entries = ldap_get_entries($this->conn, $this->result);
-
- if ($normalize) {
- return Net_LDAP3::normalize_result($entries);
- }
-
- return $entries;
- }
-
- /**
- * Wrapper for ldap_get_dn() using the current entry pointer
- */
- public function get_dn()
- {
- return $this->current ? ldap_get_dn($this->conn, $this->current) : null;
- }
-
-
- /*** Implement PHP 5 Iterator interface to make foreach work ***/
-
- function current()
- {
- $attrib = ldap_get_attributes($this->conn, $this->current);
- $attrib['dn'] = ldap_get_dn($this->conn, $this->current);
-
- return $attrib;
- }
-
- function key()
- {
- return $this->iteratorkey;
- }
-
- function rewind()
- {
- $this->iteratorkey = 0;
- $this->current = ldap_first_entry($this->conn, $this->result);
- }
-
- function next()
- {
- $this->iteratorkey++;
- $this->current = ldap_next_entry($this->conn, $this->current);
- }
-
- function valid()
- {
- return (bool)$this->current;
- }
-
-}
diff --git a/lib/ext/Net/SMTP.php b/lib/ext/Net/SMTP.php
deleted file mode 100644
index 8d0682f..0000000
--- a/lib/ext/Net/SMTP.php
+++ /dev/null
@@ -1,1342 +0,0 @@
-<?php
-/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
-// +----------------------------------------------------------------------+
-// | 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> |
-// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
-// +----------------------------------------------------------------------+
-//
-// $Id: SMTP.php 314875 2011-08-13 17:03:30Z jon $
-
-require_once 'PEAR.php';
-require_once 'Net/Socket.php';
-
-/**
- * Provides an implementation of the SMTP protocol using PEAR's
- * Net_Socket:: class.
- *
- * @package Net_SMTP
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @author Jon Parise <jon@php.net>
- * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
- *
- * @example basic.php A basic implementation of the Net_SMTP package.
- */
-class Net_SMTP
-{
- /**
- * The server to connect to.
- * @var string
- * @access public
- */
- var $host = 'localhost';
-
- /**
- * The port to connect to.
- * @var int
- * @access public
- */
- var $port = 25;
-
- /**
- * The value to give when sending EHLO or HELO.
- * @var string
- * @access public
- */
- var $localhost = 'localhost';
-
- /**
- * List of supported authentication methods, in preferential order.
- * @var array
- * @access public
- */
- var $auth_methods = array();
-
- /**
- * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
- * server supports it.
- *
- * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
- * somlFrom() and samlFrom() do not wait for a response from the
- * SMTP server but return immediately.
- *
- * @var bool
- * @access public
- */
- var $pipelining = false;
-
- /**
- * Number of pipelined commands.
- * @var int
- * @access private
- */
- var $_pipelined_commands = 0;
-
- /**
- * Should debugging output be enabled?
- * @var boolean
- * @access private
- */
- var $_debug = false;
-
- /**
- * Debug output handler.
- * @var callback
- * @access private
- */
- var $_debug_handler = null;
-
- /**
- * The socket resource being used to connect to the SMTP server.
- * @var resource
- * @access private
- */
- var $_socket = null;
-
- /**
- * Array of socket options that will be passed to Net_Socket::connect().
- * @see stream_context_create()
- * @var array
- * @access private
- */
- var $_socket_options = null;
-
- /**
- * The socket I/O timeout value in seconds.
- * @var int
- * @access private
- */
- var $_timeout = 0;
-
- /**
- * The most recent server response code.
- * @var int
- * @access private
- */
- var $_code = -1;
-
- /**
- * The most recent server response arguments.
- * @var array
- * @access private
- */
- var $_arguments = array();
-
- /**
- * Stores the SMTP server's greeting string.
- * @var string
- * @access private
- */
- var $_greeting = null;
-
- /**
- * Stores detected features of the SMTP server.
- * @var array
- * @access private
- */
- var $_esmtp = array();
-
- /**
- * Instantiates a new Net_SMTP object, overriding any defaults
- * with parameters that are passed in.
- *
- * If you have SSL support in PHP, you can connect to a server
- * over SSL using an 'ssl://' prefix:
- *
- * // 465 is a common smtps port.
- * $smtp = new Net_SMTP('ssl://mail.host.com', 465);
- * $smtp->connect();
- *
- * @param string $host The server to connect to.
- * @param integer $port The port to connect to.
- * @param string $localhost The value to give when sending EHLO or HELO.
- * @param boolean $pipeling Use SMTP command pipelining
- * @param integer $timeout Socket I/O timeout in seconds.
- * @param array $socket_options Socket stream_context_create() options.
- *
- * @access public
- * @since 1.0
- */
- function Net_SMTP($host = null, $port = null, $localhost = null,
- $pipelining = false, $timeout = 0, $socket_options = null)
- {
- if (isset($host)) {
- $this->host = $host;
- }
- if (isset($port)) {
- $this->port = $port;
- }
- if (isset($localhost)) {
- $this->localhost = $localhost;
- }
- $this->pipelining = $pipelining;
-
- $this->_socket = new Net_Socket();
- $this->_socket_options = $socket_options;
- $this->_timeout = $timeout;
-
- /* Include the Auth_SASL package. If the package is available, we
- * enable the authentication methods that depend upon it. */
- if (@include_once 'Auth/SASL.php') {
- $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5'));
- $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5'));
- }
-
- /* These standard authentication methods are always available. */
- $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false);
- $this->setAuthMethod('PLAIN', array($this, '_authPlain'), false);
- }
-
- /**
- * Set the socket I/O timeout value in seconds plus microseconds.
- *
- * @param integer $seconds Timeout value in seconds.
- * @param integer $microseconds Additional value in microseconds.
- *
- * @access public
- * @since 1.5.0
- */
- function setTimeout($seconds, $microseconds = 0) {
- return $this->_socket->setTimeout($seconds, $microseconds);
- }
-
- /**
- * Set the value of the debugging flag.
- *
- * @param boolean $debug New value for the debugging flag.
- *
- * @access public
- * @since 1.1.0
- */
- function setDebug($debug, $handler = null)
- {
- $this->_debug = $debug;
- $this->_debug_handler = $handler;
- }
-
- /**
- * Write the given debug text to the current debug output handler.
- *
- * @param string $message Debug mesage text.
- *
- * @access private
- * @since 1.3.3
- */
- function _debug($message)
- {
- if ($this->_debug) {
- if ($this->_debug_handler) {
- call_user_func_array($this->_debug_handler,
- array(&$this, $message));
- } else {
- echo "DEBUG: $message\n";
- }
- }
- }
-
- /**
- * Send the given string of data to the server.
- *
- * @param string $data The string of data to send.
- *
- * @return mixed The number of bytes that were actually written,
- * or a PEAR_Error object on failure.
- *
- * @access private
- * @since 1.1.0
- */
- function _send($data)
- {
- $this->_debug("Send: $data");
-
- $result = $this->_socket->write($data);
- if (!$result || PEAR::isError($result)) {
- $msg = ($result) ? $result->getMessage() : "unknown error";
- return PEAR::raiseError("Failed to write to socket: $msg",
- null, PEAR_ERROR_RETURN);
- }
-
- return $result;
- }
-
- /**
- * Send a command to the server with an optional string of
- * arguments. A carriage return / linefeed (CRLF) sequence will
- * be appended to each command string before it is sent to the
- * SMTP server - an error will be thrown if the command string
- * already contains any newline characters. Use _send() for
- * commands that must contain newlines.
- *
- * @param string $command The SMTP command to send to the server.
- * @param string $args A string of optional arguments to append
- * to the command.
- *
- * @return mixed The result of the _send() call.
- *
- * @access private
- * @since 1.1.0
- */
- function _put($command, $args = '')
- {
- if (!empty($args)) {
- $command .= ' ' . $args;
- }
-
- if (strcspn($command, "\r\n") !== strlen($command)) {
- return PEAR::raiseError('Commands cannot contain newlines',
- null, PEAR_ERROR_RETURN);
- }
-
- return $this->_send($command . "\r\n");
- }
-
- /**
- * Read a reply from the SMTP server. The reply consists of a response
- * code and a response message.
- *
- * @param mixed $valid The set of valid response codes. These
- * may be specified as an array of integer
- * values or as a single integer value.
- * @param bool $later Do not parse the response now, but wait
- * until the last command in the pipelined
- * command group
- *
- * @return mixed True if the server returned a valid response code or
- * a PEAR_Error object is an error condition is reached.
- *
- * @access private
- * @since 1.1.0
- *
- * @see getResponse
- */
- function _parseResponse($valid, $later = false)
- {
- $this->_code = -1;
- $this->_arguments = array();
-
- if ($later) {
- $this->_pipelined_commands++;
- return true;
- }
-
- for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
- while ($line = $this->_socket->readLine()) {
- $this->_debug("Recv: $line");
-
- /* If we receive an empty line, the connection was closed. */
- if (empty($line)) {
- $this->disconnect();
- return PEAR::raiseError('Connection was closed',
- null, PEAR_ERROR_RETURN);
- }
-
- /* Read the code and store the rest in the arguments array. */
- $code = substr($line, 0, 3);
- $this->_arguments[] = trim(substr($line, 4));
-
- /* Check the syntax of the response code. */
- if (is_numeric($code)) {
- $this->_code = (int)$code;
- } else {
- $this->_code = -1;
- break;
- }
-
- /* If this is not a multiline response, we're done. */
- if (substr($line, 3, 1) != '-') {
- break;
- }
- }
- }
-
- $this->_pipelined_commands = 0;
-
- /* Compare the server's response code with the valid code/codes. */
- if (is_int($valid) && ($this->_code === $valid)) {
- return true;
- } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
- return true;
- }
-
- return PEAR::raiseError('Invalid response code received from server',
- $this->_code, PEAR_ERROR_RETURN);
- }
-
- /**
- * Issue an SMTP command and verify its response.
- *
- * @param string $command The SMTP command string or data.
- * @param mixed $valid The set of valid response codes. These
- * may be specified as an array of integer
- * values or as a single integer value.
- *
- * @return mixed True on success or a PEAR_Error object on failure.
- *
- * @access public
- * @since 1.6.0
- */
- function command($command, $valid)
- {
- if (PEAR::isError($error = $this->_put($command))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse($valid))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Return a 2-tuple containing the last response from the SMTP server.
- *
- * @return array A two-element array: the first element contains the
- * response code as an integer and the second element
- * contains the response's arguments as a string.
- *
- * @access public
- * @since 1.1.0
- */
- function getResponse()
- {
- return array($this->_code, join("\n", $this->_arguments));
- }
-
- /**
- * Return the SMTP server's greeting string.
- *
- * @return string A string containing the greeting string, or null if a
- * greeting has not been received.
- *
- * @access public
- * @since 1.3.3
- */
- function getGreeting()
- {
- return $this->_greeting;
- }
-
- /**
- * Attempt to connect to the SMTP server.
- *
- * @param int $timeout The timeout value (in seconds) for the
- * socket connection attempt.
- * @param bool $persistent Should a persistent socket connection
- * be used?
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function connect($timeout = null, $persistent = false)
- {
- $this->_greeting = null;
- $result = $this->_socket->connect($this->host, $this->port,
- $persistent, $timeout,
- $this->_socket_options);
- if (PEAR::isError($result)) {
- return PEAR::raiseError('Failed to connect socket: ' .
- $result->getMessage());
- }
-
- /*
- * Now that we're connected, reset the socket's timeout value for
- * future I/O operations. This allows us to have different socket
- * timeout values for the initial connection (our $timeout parameter)
- * and all other socket operations.
- */
- if ($this->_timeout > 0) {
- if (PEAR::isError($error = $this->setTimeout($this->_timeout))) {
- return $error;
- }
- }
-
- if (PEAR::isError($error = $this->_parseResponse(220))) {
- return $error;
- }
-
- /* Extract and store a copy of the server's greeting string. */
- list(, $this->_greeting) = $this->getResponse();
-
- if (PEAR::isError($error = $this->_negotiate())) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Attempt to disconnect from the SMTP server.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function disconnect()
- {
- if (PEAR::isError($error = $this->_put('QUIT'))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(221))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_socket->disconnect())) {
- return PEAR::raiseError('Failed to disconnect socket: ' .
- $error->getMessage());
- }
-
- return true;
- }
-
- /**
- * Attempt to send the EHLO command and obtain a list of ESMTP
- * extensions available, and failing that just send HELO.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- *
- * @access private
- * @since 1.1.0
- */
- function _negotiate()
- {
- if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
- return $error;
- }
-
- if (PEAR::isError($this->_parseResponse(250))) {
- /* If we receive a 503 response, we're already authenticated. */
- if ($this->_code === 503) {
- return true;
- }
-
- /* If the EHLO failed, try the simpler HELO command. */
- if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
- return $error;
- }
- if (PEAR::isError($this->_parseResponse(250))) {
- return PEAR::raiseError('HELO was not accepted: ', $this->_code,
- PEAR_ERROR_RETURN);
- }
-
- return true;
- }
-
- foreach ($this->_arguments as $argument) {
- $verb = strtok($argument, ' ');
- $arguments = substr($argument, strlen($verb) + 1,
- strlen($argument) - strlen($verb) - 1);
- $this->_esmtp[$verb] = $arguments;
- }
-
- if (!isset($this->_esmtp['PIPELINING'])) {
- $this->pipelining = false;
- }
-
- return true;
- }
-
- /**
- * Returns the name of the best authentication method that the server
- * has advertised.
- *
- * @return mixed Returns a string containing the name of the best
- * supported authentication method or a PEAR_Error object
- * if a failure condition is encountered.
- * @access private
- * @since 1.1.0
- */
- function _getBestAuthMethod()
- {
- $available_methods = explode(' ', $this->_esmtp['AUTH']);
-
- foreach ($this->auth_methods as $method => $callback) {
- if (in_array($method, $available_methods)) {
- return $method;
- }
- }
-
- return PEAR::raiseError('No supported authentication methods',
- null, PEAR_ERROR_RETURN);
- }
-
- /**
- * Attempt to do SMTP authentication.
- *
- * @param string The userid to authenticate as.
- * @param string The password to authenticate with.
- * @param string The requested authentication method. If none is
- * specified, the best supported method will be used.
- * @param bool Flag indicating whether or not TLS should be attempted.
- * @param string An optional authorization identifier. If specified, this
- * identifier will be used as the authorization proxy.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
- {
- /* We can only attempt a TLS connection if one has been requested,
- * we're running PHP 5.1.0 or later, have access to the OpenSSL
- * extension, are connected to an SMTP server which supports the
- * STARTTLS extension, and aren't already connected over a secure
- * (SSL) socket connection. */
- if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') &&
- extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) &&
- strncasecmp($this->host, 'ssl://', 6) !== 0) {
- /* Start the TLS connection attempt. */
- if (PEAR::isError($result = $this->_put('STARTTLS'))) {
- return $result;
- }
- if (PEAR::isError($result = $this->_parseResponse(220))) {
- return $result;
- }
- if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
- return $result;
- } elseif ($result !== true) {
- return PEAR::raiseError('STARTTLS failed');
- }
-
- /* Send EHLO again to recieve the AUTH string from the
- * SMTP server. */
- $this->_negotiate();
- }
-
- if (empty($this->_esmtp['AUTH'])) {
- return PEAR::raiseError('SMTP server does not support authentication');
- }
-
- /* If no method has been specified, get the name of the best
- * supported method advertised by the SMTP server. */
- if (empty($method)) {
- if (PEAR::isError($method = $this->_getBestAuthMethod())) {
- /* Return the PEAR_Error object from _getBestAuthMethod(). */
- return $method;
- }
- } else {
- $method = strtoupper($method);
- if (!array_key_exists($method, $this->auth_methods)) {
- return PEAR::raiseError("$method is not a supported authentication method");
- }
- }
-
- if (!isset($this->auth_methods[$method])) {
- return PEAR::raiseError("$method is not a supported authentication method");
- }
-
- if (!is_callable($this->auth_methods[$method], false)) {
- return PEAR::raiseError("$method authentication method cannot be called");
- }
-
- if (is_array($this->auth_methods[$method])) {
- list($object, $method) = $this->auth_methods[$method];
- $result = $object->{$method}($uid, $pwd, $authz, $this);
- } else {
- $func = $this->auth_methods[$method];
- $result = $func($uid, $pwd, $authz, $this);
- }
-
- /* If an error was encountered, return the PEAR_Error object. */
- if (PEAR::isError($result)) {
- return $result;
- }
-
- return true;
- }
-
- /**
- * Add a new authentication method.
- *
- * @param string The authentication method name (e.g. 'PLAIN')
- * @param mixed The authentication callback (given as the name of a
- * function or as an (object, method name) array).
- * @param bool Should the new method be prepended to the list of
- * available methods? This is the default behavior,
- * giving the new method the highest priority.
- *
- * @return mixed True on success or a PEAR_Error object on failure.
- *
- * @access public
- * @since 1.6.0
- */
- function setAuthMethod($name, $callback, $prepend = true)
- {
- if (!is_string($name)) {
- return PEAR::raiseError('Method name is not a string');
- }
-
- if (!is_string($callback) && !is_array($callback)) {
- return PEAR::raiseError('Method callback must be string or array');
- }
-
- if (is_array($callback)) {
- if (!is_object($callback[0]) || !is_string($callback[1]))
- return PEAR::raiseError('Bad mMethod callback array');
- }
-
- if ($prepend) {
- $this->auth_methods = array_merge(array($name => $callback),
- $this->auth_methods);
- } else {
- $this->auth_methods[$name] = $callback;
- }
-
- return true;
- }
-
- /**
- * Authenticates the user using the DIGEST-MD5 method.
- *
- * @param string The userid to authenticate as.
- * @param string The password to authenticate with.
- * @param string The optional authorization proxy identifier.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access private
- * @since 1.1.0
- */
- function _authDigest_MD5($uid, $pwd, $authz = '')
- {
- if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- /* 503: Error: already authenticated */
- if ($this->_code === 503) {
- return true;
- }
- return $error;
- }
-
- $challenge = base64_decode($this->_arguments[0]);
- $digest = &Auth_SASL::factory('digestmd5');
- $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
- $this->host, "smtp",
- $authz));
-
- if (PEAR::isError($error = $this->_put($auth_str))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- return $error;
- }
-
- /* We don't use the protocol's third step because SMTP doesn't
- * allow subsequent authentication, so we just silently ignore
- * it. */
- if (PEAR::isError($error = $this->_put(''))) {
- return $error;
- }
- /* 235: Authentication successful */
- if (PEAR::isError($error = $this->_parseResponse(235))) {
- return $error;
- }
- }
-
- /**
- * Authenticates the user using the CRAM-MD5 method.
- *
- * @param string The userid to authenticate as.
- * @param string The password to authenticate with.
- * @param string The optional authorization proxy identifier.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access private
- * @since 1.1.0
- */
- function _authCRAM_MD5($uid, $pwd, $authz = '')
- {
- if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- /* 503: Error: already authenticated */
- if ($this->_code === 503) {
- return true;
- }
- return $error;
- }
-
- $challenge = base64_decode($this->_arguments[0]);
- $cram = &Auth_SASL::factory('crammd5');
- $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
-
- if (PEAR::isError($error = $this->_put($auth_str))) {
- return $error;
- }
-
- /* 235: Authentication successful */
- if (PEAR::isError($error = $this->_parseResponse(235))) {
- return $error;
- }
- }
-
- /**
- * Authenticates the user using the LOGIN method.
- *
- * @param string The userid to authenticate as.
- * @param string The password to authenticate with.
- * @param string The optional authorization proxy identifier.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access private
- * @since 1.1.0
- */
- function _authLogin($uid, $pwd, $authz = '')
- {
- if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- /* 503: Error: already authenticated */
- if ($this->_code === 503) {
- return true;
- }
- return $error;
- }
-
- if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- return $error;
- }
-
- if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
- return $error;
- }
-
- /* 235: Authentication successful */
- if (PEAR::isError($error = $this->_parseResponse(235))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Authenticates the user using the PLAIN method.
- *
- * @param string The userid to authenticate as.
- * @param string The password to authenticate with.
- * @param string The optional authorization proxy identifier.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access private
- * @since 1.1.0
- */
- function _authPlain($uid, $pwd, $authz = '')
- {
- if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
- return $error;
- }
- /* 334: Continue authentication request */
- if (PEAR::isError($error = $this->_parseResponse(334))) {
- /* 503: Error: already authenticated */
- if ($this->_code === 503) {
- return true;
- }
- return $error;
- }
-
- $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd);
-
- if (PEAR::isError($error = $this->_put($auth_str))) {
- return $error;
- }
-
- /* 235: Authentication successful */
- if (PEAR::isError($error = $this->_parseResponse(235))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Send the HELO command.
- *
- * @param string The domain name to say we are.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function helo($domain)
- {
- if (PEAR::isError($error = $this->_put('HELO', $domain))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Return the list of SMTP service extensions advertised by the server.
- *
- * @return array The list of SMTP service extensions.
- * @access public
- * @since 1.3
- */
- function getServiceExtensions()
- {
- return $this->_esmtp;
- }
-
- /**
- * Send the MAIL FROM: command.
- *
- * @param string $sender The sender (reverse path) to set.
- * @param string $params String containing additional MAIL parameters,
- * such as the NOTIFY flags defined by RFC 1891
- * or the VERP protocol.
- *
- * If $params is an array, only the 'verp' option
- * is supported. If 'verp' is true, the XVERP
- * parameter is appended to the MAIL command. If
- * the 'verp' value is a string, the full
- * XVERP=value parameter is appended.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function mailFrom($sender, $params = null)
- {
- $args = "FROM:<$sender>";
-
- /* Support the deprecated array form of $params. */
- if (is_array($params) && isset($params['verp'])) {
- /* XVERP */
- if ($params['verp'] === true) {
- $args .= ' XVERP';
-
- /* XVERP=something */
- } elseif (trim($params['verp'])) {
- $args .= ' XVERP=' . $params['verp'];
- }
- } elseif (is_string($params) && !empty($params)) {
- $args .= ' ' . $params;
- }
-
- if (PEAR::isError($error = $this->_put('MAIL', $args))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Send the RCPT TO: command.
- *
- * @param string $recipient The recipient (forward path) to add.
- * @param string $params String containing additional RCPT parameters,
- * such as the NOTIFY flags defined by RFC 1891.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- *
- * @access public
- * @since 1.0
- */
- function rcptTo($recipient, $params = null)
- {
- $args = "TO:<$recipient>";
- if (is_string($params)) {
- $args .= ' ' . $params;
- }
-
- if (PEAR::isError($error = $this->_put('RCPT', $args))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Quote the data so that it meets SMTP standards.
- *
- * This is provided as a separate public function to facilitate
- * easier overloading for the cases where it is desirable to
- * customize the quoting behavior.
- *
- * @param string $data The message text to quote. The string must be passed
- * by reference, and the text will be modified in place.
- *
- * @access public
- * @since 1.2
- */
- function quotedata(&$data)
- {
- /* Change Unix (\n) and Mac (\r) linefeeds into
- * Internet-standard CRLF (\r\n) linefeeds. */
- $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
-
- /* Because a single leading period (.) signifies an end to the
- * data, legitimate leading periods need to be "doubled"
- * (e.g. '..'). */
- $data = str_replace("\n.", "\n..", $data);
- }
-
- /**
- * Send the DATA command.
- *
- * @param mixed $data The message data, either as a string or an open
- * file resource.
- * @param string $headers The message headers. If $headers is provided,
- * $data is assumed to contain only body data.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function data($data, $headers = null)
- {
- /* Verify that $data is a supported type. */
- if (!is_string($data) && !is_resource($data)) {
- return PEAR::raiseError('Expected a string or file resource');
- }
-
- /* Start by considering the size of the optional headers string. We
- * also account for the addition 4 character "\r\n\r\n" separator
- * sequence. */
- $size = (is_null($headers)) ? 0 : strlen($headers) + 4;
-
- if (is_resource($data)) {
- $stat = fstat($data);
- if ($stat === false) {
- return PEAR::raiseError('Failed to get file size');
- }
- $size += $stat['size'];
- } else {
- $size += strlen($data);
- }
-
- /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
- * that no fixed maximum message size is in force". Furthermore, it
- * says that if "the parameter is omitted no information is conveyed
- * about the server's fixed maximum message size". */
- $limit = (isset($this->_esmtp['SIZE'])) ? $this->_esmtp['SIZE'] : 0;
- if ($limit > 0 && $size >= $limit) {
- $this->disconnect();
- return PEAR::raiseError('Message size exceeds server limit');
- }
-
- /* Initiate the DATA command. */
- if (PEAR::isError($error = $this->_put('DATA'))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(354))) {
- return $error;
- }
-
- /* If we have a separate headers string, send it first. */
- if (!is_null($headers)) {
- $this->quotedata($headers);
- if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) {
- return $result;
- }
- }
-
- /* Now we can send the message body data. */
- if (is_resource($data)) {
- /* Stream the contents of the file resource out over our socket
- * connection, line by line. Each line must be run through the
- * quoting routine. */
- while (strlen($line = fread($data, 8192)) > 0) {
- /* If the last character is an newline, we need to grab the
- * next character to check to see if it is a period. */
- while (!feof($data)) {
- $char = fread($data, 1);
- $line .= $char;
- if ($char != "\n") {
- break;
- }
- }
- $this->quotedata($line);
- if (PEAR::isError($result = $this->_send($line))) {
- return $result;
- }
- }
- } else {
- /*
- * Break up the data by sending one chunk (up to 512k) at a time.
- * This approach reduces our peak memory usage.
- */
- for ($offset = 0; $offset < $size;) {
- $end = $offset + 512000;
-
- /*
- * Ensure we don't read beyond our data size or span multiple
- * lines. quotedata() can't properly handle character data
- * that's split across two line break boundaries.
- */
- if ($end >= $size) {
- $end = $size;
- } else {
- for (; $end < $size; $end++) {
- if ($data[$end] != "\n") {
- break;
- }
- }
- }
-
- /* Extract our chunk and run it through the quoting routine. */
- $chunk = substr($data, $offset, $end - $offset);
- $this->quotedata($chunk);
-
- /* If we run into a problem along the way, abort. */
- if (PEAR::isError($result = $this->_send($chunk))) {
- return $result;
- }
-
- /* Advance the offset to the end of this chunk. */
- $offset = $end;
- }
- }
-
- /* Finally, send the DATA terminator sequence. */
- if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) {
- return $result;
- }
-
- /* Verify that the data was successfully received by the server. */
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Send the SEND FROM: command.
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.2.6
- */
- function sendFrom($path)
- {
- if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Backwards-compatibility wrapper for sendFrom().
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- *
- * @access public
- * @since 1.0
- * @deprecated 1.2.6
- */
- function send_from($path)
- {
- return sendFrom($path);
- }
-
- /**
- * Send the SOML FROM: command.
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.2.6
- */
- function somlFrom($path)
- {
- if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Backwards-compatibility wrapper for somlFrom().
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- *
- * @access public
- * @since 1.0
- * @deprecated 1.2.6
- */
- function soml_from($path)
- {
- return somlFrom($path);
- }
-
- /**
- * Send the SAML FROM: command.
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.2.6
- */
- function samlFrom($path)
- {
- if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Backwards-compatibility wrapper for samlFrom().
- *
- * @param string The reverse path to send.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- *
- * @access public
- * @since 1.0
- * @deprecated 1.2.6
- */
- function saml_from($path)
- {
- return samlFrom($path);
- }
-
- /**
- * Send the RSET command.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function rset()
- {
- if (PEAR::isError($error = $this->_put('RSET'))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Send the VRFY command.
- *
- * @param string The string to verify
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function vrfy($string)
- {
- /* Note: 251 is also a valid response code */
- if (PEAR::isError($error = $this->_put('VRFY', $string))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Send the NOOP command.
- *
- * @return mixed Returns a PEAR_Error with an error message on any
- * kind of failure, or true on success.
- * @access public
- * @since 1.0
- */
- function noop()
- {
- if (PEAR::isError($error = $this->_put('NOOP'))) {
- return $error;
- }
- if (PEAR::isError($error = $this->_parseResponse(250))) {
- return $error;
- }
-
- return true;
- }
-
- /**
- * Backwards-compatibility method. identifySender()'s functionality is
- * now handled internally.
- *
- * @return boolean This method always return true.
- *
- * @access public
- * @since 1.0
- */
- function identifySender()
- {
- return true;
- }
-
-}
diff --git a/lib/ext/Net/URL2.php b/lib/ext/Net/URL2.php
deleted file mode 100755
index 9989404..0000000
--- a/lib/ext/Net/URL2.php
+++ /dev/null
@@ -1,942 +0,0 @@
-<?php
-/**
- * Net_URL2, a class representing a URL as per RFC 3986.
- *
- * PHP version 5
- *
- * LICENSE:
- *
- * Copyright (c) 2007-2009, Peytz & Co. A/S
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * 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.
- * * Neither the name of the Net_URL2 nor the names of its contributors may
- * 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.
- *
- * @category Networking
- * @package Net_URL2
- * @author Christian Schmidt <schmidt@php.net>
- * @copyright 2007-2009 Peytz & Co. A/S
- * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $
- * @link http://www.rfc-editor.org/rfc/rfc3986.txt
- */
-
-/**
- * Represents a URL as per RFC 3986.
- *
- * @category Networking
- * @package Net_URL2
- * @author Christian Schmidt <schmidt@php.net>
- * @copyright 2007-2009 Peytz & Co. A/S
- * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
- * @version Release: @package_version@
- * @link http://pear.php.net/package/Net_URL2
- */
-class Net_URL2
-{
- /**
- * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
- * is true.
- */
- const OPTION_STRICT = 'strict';
-
- /**
- * Represent arrays in query using PHP's [] notation. Default is true.
- */
- const OPTION_USE_BRACKETS = 'use_brackets';
-
- /**
- * URL-encode query variable keys. Default is true.
- */
- const OPTION_ENCODE_KEYS = 'encode_keys';
-
- /**
- * Query variable separators when parsing the query string. Every character
- * is considered a separator. Default is "&".
- */
- const OPTION_SEPARATOR_INPUT = 'input_separator';
-
- /**
- * Query variable separator used when generating the query string. Default
- * is "&".
- */
- const OPTION_SEPARATOR_OUTPUT = 'output_separator';
-
- /**
- * Default options corresponds to how PHP handles $_GET.
- */
- private $_options = array(
- self::OPTION_STRICT => true,
- self::OPTION_USE_BRACKETS => true,
- self::OPTION_ENCODE_KEYS => true,
- self::OPTION_SEPARATOR_INPUT => '&',
- self::OPTION_SEPARATOR_OUTPUT => '&',
- );
-
- /**
- * @var string|bool
- */
- private $_scheme = false;
-
- /**
- * @var string|bool
- */
- private $_userinfo = false;
-
- /**
- * @var string|bool
- */
- private $_host = false;
-
- /**
- * @var string|bool
- */
- private $_port = false;
-
- /**
- * @var string
- */
- private $_path = '';
-
- /**
- * @var string|bool
- */
- private $_query = false;
-
- /**
- * @var string|bool
- */
- private $_fragment = false;
-
- /**
- * Constructor.
- *
- * @param string $url an absolute or relative URL
- * @param array $options an array of OPTION_xxx constants
- *
- * @return $this
- * @uses self::parseUrl()
- */
- public function __construct($url, array $options = array())
- {
- foreach ($options as $optionName => $value) {
- if (array_key_exists($optionName, $this->_options)) {
- $this->_options[$optionName] = $value;
- }
- }
-
- $this->parseUrl($url);
- }
-
- /**
- * Magic Setter.
- *
- * This method will magically set the value of a private variable ($var)
- * with the value passed as the args
- *
- * @param string $var The private variable to set.
- * @param mixed $arg An argument of any type.
- * @return void
- */
- public function __set($var, $arg)
- {
- $method = 'set' . $var;
- if (method_exists($this, $method)) {
- $this->$method($arg);
- }
- }
-
- /**
- * Magic Getter.
- *
- * This is the magic get method to retrieve the private variable
- * that was set by either __set() or it's setter...
- *
- * @param string $var The property name to retrieve.
- * @return mixed $this->$var Either a boolean false if the
- * property is not set or the value
- * of the private property.
- */
- public function __get($var)
- {
- $method = 'get' . $var;
- if (method_exists($this, $method)) {
- return $this->$method();
- }
-
- return false;
- }
-
- /**
- * Returns the scheme, e.g. "http" or "urn", or false if there is no
- * scheme specified, i.e. if this is a relative URL.
- *
- * @return string|bool
- */
- public function getScheme()
- {
- return $this->_scheme;
- }
-
- /**
- * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
- * scheme specified, i.e. if this is a relative URL.
- *
- * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
- * scheme specified, i.e. if this is a relative
- * URL
- *
- * @return $this
- * @see getScheme()
- */
- public function setScheme($scheme)
- {
- $this->_scheme = $scheme;
- return $this;
- }
-
- /**
- * Returns the user part of the userinfo part (the part preceding the first
- * ":"), or false if there is no userinfo part.
- *
- * @return string|bool
- */
- public function getUser()
- {
- return $this->_userinfo !== false
- ? preg_replace('@:.*$@', '', $this->_userinfo)
- : false;
- }
-
- /**
- * Returns the password part of the userinfo part (the part after the first
- * ":"), or false if there is no userinfo part (i.e. the URL does not
- * contain "@" in front of the hostname) or the userinfo part does not
- * contain ":".
- *
- * @return string|bool
- */
- public function getPassword()
- {
- return $this->_userinfo !== false
- ? substr(strstr($this->_userinfo, ':'), 1)
- : false;
- }
-
- /**
- * Returns the userinfo part, or false if there is none, i.e. if the
- * authority part does not contain "@".
- *
- * @return string|bool
- */
- public function getUserinfo()
- {
- return $this->_userinfo;
- }
-
- /**
- * Sets the userinfo part. If two arguments are passed, they are combined
- * in the userinfo part as username ":" password.
- *
- * @param string|bool $userinfo userinfo or username
- * @param string|bool $password optional password, or false
- *
- * @return $this
- */
- public function setUserinfo($userinfo, $password = false)
- {
- $this->_userinfo = $userinfo;
- if ($password !== false) {
- $this->_userinfo .= ':' . $password;
- }
- return $this;
- }
-
- /**
- * Returns the host part, or false if there is no authority part, e.g.
- * relative URLs.
- *
- * @return string|bool a hostname, an IP address, or false
- */
- public function getHost()
- {
- return $this->_host;
- }
-
- /**
- * Sets the host part. Specify false if there is no authority part, e.g.
- * relative URLs.
- *
- * @param string|bool $host a hostname, an IP address, or false
- *
- * @return $this
- */
- public function setHost($host)
- {
- $this->_host = $host;
- return $this;
- }
-
- /**
- * Returns the port number, or false if there is no port number specified,
- * i.e. if the default port is to be used.
- *
- * @return string|bool
- */
- public function getPort()
- {
- return $this->_port;
- }
-
- /**
- * Sets the port number. Specify false if there is no port number specified,
- * i.e. if the default port is to be used.
- *
- * @param string|bool $port a port number, or false
- *
- * @return $this
- */
- public function setPort($port)
- {
- $this->_port = $port;
- return $this;
- }
-
- /**
- * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
- * false if there is no authority.
- *
- * @return string|bool
- */
- public function getAuthority()
- {
- if (!$this->_host) {
- return false;
- }
-
- $authority = '';
-
- if ($this->_userinfo !== false) {
- $authority .= $this->_userinfo . '@';
- }
-
- $authority .= $this->_host;
-
- if ($this->_port !== false) {
- $authority .= ':' . $this->_port;
- }
-
- return $authority;
- }
-
- /**
- * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
- * false if there is no authority.
- *
- * @param string|false $authority a hostname or an IP addresse, possibly
- * with userinfo prefixed and port number
- * appended, e.g. "foo:bar@example.org:81".
- *
- * @return $this
- */
- public function setAuthority($authority)
- {
- $this->_userinfo = false;
- $this->_host = false;
- $this->_port = false;
- if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
- if ($reg[1]) {
- $this->_userinfo = $reg[2];
- }
-
- $this->_host = $reg[3];
- if (isset($reg[5])) {
- $this->_port = $reg[5];
- }
- }
- return $this;
- }
-
- /**
- * Returns the path part (possibly an empty string).
- *
- * @return string
- */
- public function getPath()
- {
- return $this->_path;
- }
-
- /**
- * Sets the path part (possibly an empty string).
- *
- * @param string $path a path
- *
- * @return $this
- */
- public function setPath($path)
- {
- $this->_path = $path;
- return $this;
- }
-
- /**
- * Returns the query string (excluding the leading "?"), or false if "?"
- * is not present in the URL.
- *
- * @return string|bool
- * @see self::getQueryVariables()
- */
- public function getQuery()
- {
- return $this->_query;
- }
-
- /**
- * Sets the query string (excluding the leading "?"). Specify false if "?"
- * is not present in the URL.
- *
- * @param string|bool $query a query string, e.g. "foo=1&bar=2"
- *
- * @return $this
- * @see self::setQueryVariables()
- */
- public function setQuery($query)
- {
- $this->_query = $query;
- return $this;
- }
-
- /**
- * Returns the fragment name, or false if "#" is not present in the URL.
- *
- * @return string|bool
- */
- public function getFragment()
- {
- return $this->_fragment;
- }
-
- /**
- * Sets the fragment name. Specify false if "#" is not present in the URL.
- *
- * @param string|bool $fragment a fragment excluding the leading "#", or
- * false
- *
- * @return $this
- */
- public function setFragment($fragment)
- {
- $this->_fragment = $fragment;
- return $this;
- }
-
- /**
- * Returns the query string like an array as the variables would appear in
- * $_GET in a PHP script. If the URL does not contain a "?", an empty array
- * is returned.
- *
- * @return array
- */
- public function getQueryVariables()
- {
- $pattern = '/[' .
- preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
- ']/';
- $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
- $return = array();
-
- foreach ($parts as $part) {
- if (strpos($part, '=') !== false) {
- list($key, $value) = explode('=', $part, 2);
- } else {
- $key = $part;
- $value = null;
- }
-
- if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
- $key = rawurldecode($key);
- }
- $value = rawurldecode($value);
-
- if ($this->getOption(self::OPTION_USE_BRACKETS) &&
- preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
-
- $key = $matches[1];
- $idx = $matches[2];
-
- // Ensure is an array
- if (empty($return[$key]) || !is_array($return[$key])) {
- $return[$key] = array();
- }
-
- // Add data
- if ($idx === '') {
- $return[$key][] = $value;
- } else {
- $return[$key][$idx] = $value;
- }
- } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
- && !empty($return[$key])
- ) {
- $return[$key] = (array) $return[$key];
- $return[$key][] = $value;
- } else {
- $return[$key] = $value;
- }
- }
-
- return $return;
- }
-
- /**
- * Sets the query string to the specified variable in the query string.
- *
- * @param array $array (name => value) array
- *
- * @return $this
- */
- public function setQueryVariables(array $array)
- {
- if (!$array) {
- $this->_query = false;
- } else {
- $this->_query = $this->buildQuery(
- $array,
- $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
- );
- }
- return $this;
- }
-
- /**
- * Sets the specified variable in the query string.
- *
- * @param string $name variable name
- * @param mixed $value variable value
- *
- * @return $this
- */
- public function setQueryVariable($name, $value)
- {
- $array = $this->getQueryVariables();
- $array[$name] = $value;
- $this->setQueryVariables($array);
- return $this;
- }
-
- /**
- * Removes the specifed variable from the query string.
- *
- * @param string $name a query string variable, e.g. "foo" in "?foo=1"
- *
- * @return void
- */
- public function unsetQueryVariable($name)
- {
- $array = $this->getQueryVariables();
- unset($array[$name]);
- $this->setQueryVariables($array);
- }
-
- /**
- * Returns a string representation of this URL.
- *
- * @return string
- */
- public function getURL()
- {
- // See RFC 3986, section 5.3
- $url = "";
-
- if ($this->_scheme !== false) {
- $url .= $this->_scheme . ':';
- }
-
- $authority = $this->getAuthority();
- if ($authority !== false) {
- $url .= '//' . $authority;
- }
- $url .= $this->_path;
-
- if ($this->_query !== false) {
- $url .= '?' . $this->_query;
- }
-
- if ($this->_fragment !== false) {
- $url .= '#' . $this->_fragment;
- }
-
- return $url;
- }
-
- /**
- * Returns a string representation of this URL.
- *
- * @return string
- * @see toString()
- */
- public function __toString()
- {
- return $this->getURL();
- }
-
- /**
- * Returns a normalized string representation of this URL. This is useful
- * for comparison of URLs.
- *
- * @return string
- */
- public function getNormalizedURL()
- {
- $url = clone $this;
- $url->normalize();
- return $url->getUrl();
- }
-
- /**
- * Returns a normalized Net_URL2 instance.
- *
- * @return Net_URL2
- */
- public function normalize()
- {
- // See RFC 3886, section 6
-
- // Schemes are case-insensitive
- if ($this->_scheme) {
- $this->_scheme = strtolower($this->_scheme);
- }
-
- // Hostnames are case-insensitive
- if ($this->_host) {
- $this->_host = strtolower($this->_host);
- }
-
- // Remove default port number for known schemes (RFC 3986, section 6.2.3)
- if ($this->_port &&
- $this->_scheme &&
- $this->_port == getservbyname($this->_scheme, 'tcp')) {
-
- $this->_port = false;
- }
-
- // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
- foreach (array('_userinfo', '_host', '_path') as $part) {
- if ($this->$part) {
- $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
- 'strtoupper("\0")',
- $this->$part);
- }
- }
-
- // Path segment normalization (RFC 3986, section 6.2.2.3)
- $this->_path = self::removeDotSegments($this->_path);
-
- // Scheme based normalization (RFC 3986, section 6.2.3)
- if ($this->_host && !$this->_path) {
- $this->_path = '/';
- }
- }
-
- /**
- * Returns whether this instance represents an absolute URL.
- *
- * @return bool
- */
- public function isAbsolute()
- {
- return (bool) $this->_scheme;
- }
-
- /**
- * Returns an Net_URL2 instance representing an absolute URL relative to
- * this URL.
- *
- * @param Net_URL2|string $reference relative URL
- *
- * @return Net_URL2
- */
- public function resolve($reference)
- {
- if (!$reference instanceof Net_URL2) {
- $reference = new self($reference);
- }
- if (!$this->isAbsolute()) {
- throw new Exception('Base-URL must be absolute');
- }
-
- // A non-strict parser may ignore a scheme in the reference if it is
- // identical to the base URI's scheme.
- if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
- $reference->_scheme = false;
- }
-
- $target = new self('');
- if ($reference->_scheme !== false) {
- $target->_scheme = $reference->_scheme;
- $target->setAuthority($reference->getAuthority());
- $target->_path = self::removeDotSegments($reference->_path);
- $target->_query = $reference->_query;
- } else {
- $authority = $reference->getAuthority();
- if ($authority !== false) {
- $target->setAuthority($authority);
- $target->_path = self::removeDotSegments($reference->_path);
- $target->_query = $reference->_query;
- } else {
- if ($reference->_path == '') {
- $target->_path = $this->_path;
- if ($reference->_query !== false) {
- $target->_query = $reference->_query;
- } else {
- $target->_query = $this->_query;
- }
- } else {
- if (substr($reference->_path, 0, 1) == '/') {
- $target->_path = self::removeDotSegments($reference->_path);
- } else {
- // Merge paths (RFC 3986, section 5.2.3)
- if ($this->_host !== false && $this->_path == '') {
- $target->_path = '/' . $this->_path;
- } else {
- $i = strrpos($this->_path, '/');
- if ($i !== false) {
- $target->_path = substr($this->_path, 0, $i + 1);
- }
- $target->_path .= $reference->_path;
- }
- $target->_path = self::removeDotSegments($target->_path);
- }
- $target->_query = $reference->_query;
- }
- $target->setAuthority($this->getAuthority());
- }
- $target->_scheme = $this->_scheme;
- }
-
- $target->_fragment = $reference->_fragment;
-
- return $target;
- }
-
- /**
- * Removes dots as described in RFC 3986, section 5.2.4, e.g.
- * "/foo/../bar/baz" => "/bar/baz"
- *
- * @param string $path a path
- *
- * @return string a path
- */
- public static function removeDotSegments($path)
- {
- $output = '';
-
- // Make sure not to be trapped in an infinite loop due to a bug in this
- // method
- $j = 0;
- while ($path && $j++ < 100) {
- if (substr($path, 0, 2) == './') {
- // Step 2.A
- $path = substr($path, 2);
- } elseif (substr($path, 0, 3) == '../') {
- // Step 2.A
- $path = substr($path, 3);
- } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
- // Step 2.B
- $path = '/' . substr($path, 3);
- } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
- // Step 2.C
- $path = '/' . substr($path, 4);
- $i = strrpos($output, '/');
- $output = $i === false ? '' : substr($output, 0, $i);
- } elseif ($path == '.' || $path == '..') {
- // Step 2.D
- $path = '';
- } else {
- // Step 2.E
- $i = strpos($path, '/');
- if ($i === 0) {
- $i = strpos($path, '/', 1);
- }
- if ($i === false) {
- $i = strlen($path);
- }
- $output .= substr($path, 0, $i);
- $path = substr($path, $i);
- }
- }
-
- return $output;
- }
-
- /**
- * Percent-encodes all non-alphanumeric characters except these: _ . - ~
- * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
- * 5.2.x and earlier.
- *
- * @param $raw the string to encode
- * @return string
- */
- public static function urlencode($string)
- {
- $encoded = rawurlencode($string);
-
- // This is only necessary in PHP < 5.3.
- $encoded = str_replace('%7E', '~', $encoded);
- return $encoded;
- }
-
- /**
- * Returns a Net_URL2 instance representing the canonical URL of the
- * currently executing PHP script.
- *
- * @return string
- */
- public static function getCanonical()
- {
- if (!isset($_SERVER['REQUEST_METHOD'])) {
- // ALERT - no current URL
- throw new Exception('Script was not called through a webserver');
- }
-
- // Begin with a relative URL
- $url = new self($_SERVER['PHP_SELF']);
- $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
- $url->_host = $_SERVER['SERVER_NAME'];
- $port = $_SERVER['SERVER_PORT'];
- if ($url->_scheme == 'http' && $port != 80 ||
- $url->_scheme == 'https' && $port != 443) {
-
- $url->_port = $port;
- }
- return $url;
- }
-
- /**
- * Returns the URL used to retrieve the current request.
- *
- * @return string
- */
- public static function getRequestedURL()
- {
- return self::getRequested()->getUrl();
- }
-
- /**
- * Returns a Net_URL2 instance representing the URL used to retrieve the
- * current request.
- *
- * @return Net_URL2
- */
- public static function getRequested()
- {
- if (!isset($_SERVER['REQUEST_METHOD'])) {
- // ALERT - no current URL
- throw new Exception('Script was not called through a webserver');
- }
-
- // Begin with a relative URL
- $url = new self($_SERVER['REQUEST_URI']);
- $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
- // Set host and possibly port
- $url->setAuthority($_SERVER['HTTP_HOST']);
- return $url;
- }
-
- /**
- * Returns the value of the specified option.
- *
- * @param string $optionName The name of the option to retrieve
- *
- * @return mixed
- */
- public function getOption($optionName)
- {
- return isset($this->_options[$optionName])
- ? $this->_options[$optionName] : false;
- }
-
- /**
- * A simple version of http_build_query in userland. The encoded string is
- * percentage encoded according to RFC 3986.
- *
- * @param array $data An array, which has to be converted into
- * QUERY_STRING. Anything is possible.
- * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT}
- * @param string $key For stacked values (arrays in an array).
- *
- * @return string
- */
- protected function buildQuery(array $data, $separator, $key = null)
- {
- $query = array();
- foreach ($data as $name => $value) {
- if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
- $name = rawurlencode($name);
- }
- if ($key !== null) {
- if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
- $name = $key . '[' . $name . ']';
- } else {
- $name = $key;
- }
- }
- if (is_array($value)) {
- $query[] = $this->buildQuery($value, $separator, $name);
- } else {
- $query[] = $name . '=' . rawurlencode($value);
- }
- }
- return implode($separator, $query);
- }
-
- /**
- * This method uses a funky regex to parse the url into the designated parts.
- *
- * @param string $url
- *
- * @return void
- * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
- * self::$_fragment
- * @see self::__construct()
- */
- protected function parseUrl($url)
- {
- // The regular expression is copied verbatim from RFC 3986, appendix B.
- // The expression does not validate the URL but matches any string.
- preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
- $url,
- $matches);
-
- // "path" is always present (possibly as an empty string); the rest
- // are optional.
- $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
- $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
- $this->_path = $matches[5];
- $this->_query = !empty($matches[6]) ? $matches[7] : false;
- $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
- }
-}
diff --git a/lib/functions.php b/lib/functions.php
index 62e368e..eb4d093 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -1,133 +1,138 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
// Initialization and basic functions
// application constants
define('KADM_START', microtime(true));
define('KADM_VERSION', '0.1');
define('KADM_CHARSET', 'utf-8');
define('INSTALL_PATH', dirname(__FILE__));
// Check critical PHP settings here.
$crit_opts = array(
'mbstring.func_overload' => 0,
'magic_quotes_runtime' => 0,
);
foreach ($crit_opts as $optname => $optval) {
if ($optval != ini_get($optname)) {
die("ERROR: Wrong '$optname' option value!");
}
}
$include_path = INSTALL_PATH . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/client' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/api' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/ext' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}
ini_set('error_reporting', E_ALL &~ E_NOTICE &~ E_STRICT);
ini_set('error_log', INSTALL_PATH . '/../logs/errors');
// Set internal charset
mb_internal_encoding(KADM_CHARSET);
@mb_regex_encoding(KADM_CHARSET);
+// include composer autoloader (if available)
+if (@file_exists(INSTALL_PATH . '/../vendor/autoload.php')) {
+ require INSTALL_PATH . '/../vendor/autoload.php';
+}
+
// register autoloader
function class_autoloader($classname)
{
$classname = preg_replace('/(Net|HTTP|SQL)_(.+)/', "\\1/\\2", $classname);
if ($fp = @fopen("$classname.php", 'r', true)) {
include_once "$classname.php";
fclose($fp);
return true;
}
return false;
}
spl_autoload_register('class_autoloader');
/**
* Prints debug info into the 'console' log
*/
function console()
{
$args = func_get_args();
$msg = array();
foreach ($args as $arg) {
$msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
}
write_log('console', join(";\n", $msg));
}
/**
* Appends a line to a log file in the logs directory.
* Date will be added automatically to the line.
*
* @param string $name Name of the log file
* @param mixed $line Line to append
*/
function write_log($name, $line)
{
if (!is_string($line)) {
$line = var_export($line, true);
}
$log_dir = dirname(__FILE__) . '/../logs';
$logfile = $log_dir . '/' . $name;
$date = date('d-M-Y H:i:s O');
$sess_id = session_id();
$logline = sprintf("[%s]%s: %s\n", $date, $sess_id ? "($sess_id)" : '', $line);
if ($fp = @fopen($logfile, 'a')) {
fwrite($fp, $logline);
fflush($fp);
fclose($fp);
return;
}
if ($name == 'errors') {
// send error to PHPs error handler if write to file didn't succeed
trigger_error($line, E_USER_ERROR);
}
}
function timer($time = null, $label = '')
{
$now = microtime(true);
if ($time) {
console(($label ? $label.' ' : '') . sprintf('%.4f', $now - $time));
}
return $now;
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 10:06 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
9948606
Default Alt Text
(522 KB)

Event Timeline