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 logs +$ chown 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 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ -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 "_", 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 @@ - - * 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 - * @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 - * @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: - *
    - *
  • 'adapter' - adapter to use (string)
  • - *
  • 'connect_timeout' - Connection timeout in seconds (integer)
  • - *
  • 'timeout' - Total number of seconds a request can take. - * Use 0 for no limit, should be greater than - * 'connect_timeout' if set (integer)
  • - *
  • 'use_brackets' - Whether to append [] to array variable names (bool)
  • - *
  • 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)
  • - *
  • 'buffer_size' - Buffer size to use for reading and writing (int)
  • - *
  • '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)
  • - *
  • 'proxy_host' - Proxy server host (string)
  • - *
  • 'proxy_port' - Proxy server port (integer)
  • - *
  • 'proxy_user' - Proxy auth username (string)
  • - *
  • 'proxy_password' - Proxy auth password (string)
  • - *
  • 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)
  • - *
  • 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)
  • - *
  • 'ssl_verify_host' - Whether to check that Common Name in SSL - * certificate matches host name (bool)
  • - *
  • 'ssl_cafile' - Cerificate Authority file to verify the peer - * with (use with 'ssl_verify_peer') (string)
  • - *
  • 'ssl_capath' - Directory holding multiple Certificate - * Authority files (string)
  • - *
  • 'ssl_local_cert' - Name of a file containing local cerificate (string)
  • - *
  • 'ssl_passphrase' - Passphrase with which local certificate - * was encoded (string)
  • - *
  • 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6 - * in using URL without query string in digest - * authentication (boolean)
  • - *
  • 'follow_redirects' - Whether to automatically follow HTTP Redirects (boolean)
  • - *
  • 'max_redirects' - Maximum number of redirects to follow (integer)
  • - *
  • '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)
  • - *
- * - * @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. - * - * - * $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 - * - * - * @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: - *
    - *
  • 'connect' - after connection to remote server, - * data is the destination (string)
  • - *
  • 'disconnect' - after disconnection from server
  • - *
  • 'sentHeaders' - after sending the request headers, - * data is the headers sent (string)
  • - *
  • 'sentBodyPart' - after sending a part of the request body, - * data is the length of that part (int)
  • - *
  • 'sentBody' - after sending the whole request body, - * data is request body length (int)
  • - *
  • 'receivedHeaders' - after receiving the response headers, - * data is HTTP_Request2_Response object
  • - *
  • 'receivedBodyPart' - after receiving a part of the response - * body, data is that part (string)
  • - *
  • 'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still - * encoded by Content-Encoding
  • - *
  • 'receivedBody' - after receiving the complete response - * body, data is HTTP_Request2_Response object
  • - *
- * 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 - * - * $request->setAdapter('curl'); - * - * 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 @@ - - * 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 - * @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 - * @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 @@ - - * 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 - * @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 - * @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 @@ - - * 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 - * @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() - * - * $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(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @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 @@ - - * 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 - * @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 - * @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 @@ - - * 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 - * @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 - * @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 @@ - - * 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 - * @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 @@ - - * 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 - * @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 - * @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 @@ - - * 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 - * @author Alexey Borzov - * @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: - * - * 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(); - * - * - * A more complex example with PEAR Log: - * - * 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(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author David Jean Louis - * @author Alexey Borzov - * @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 @@ - - * 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 - * @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: - * - * $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()); - * - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @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 @@ - - * @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('=((||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 @@ - - * @author Chuck Hagenbuch - * @author Chuck Hagenbuch - * @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 - // ... 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* - * - * 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('/(?@. 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 @@ - - * @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 @@ - - * @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 @@ - - * @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 @@ - | -// +----------------------------------------------------------------------+ - -/** - * 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 @@ - - * @author Chuck Hagenbuch - * @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 @@ - - * @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 - * @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 @@ -. | - | | - | PURPOSE: | - | Provide advanced functionality for accessing LDAP directories | - | | - +-----------------------------------------------------------------------+ - | Authors: Thomas Bruederli | - | Aleksander Machniak | - | Jeroen van Meeuwen | - +-----------------------------------------------------------------------+ -*/ - -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 @@ -. | - | | - | PURPOSE: | - | Provide advanced functionality for accessing LDAP directories | - | | - +-----------------------------------------------------------------------+ - | Authors: Thomas Bruederli | - | Jeroen van Meeuwen | - +-----------------------------------------------------------------------+ -*/ - -/** - * 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 @@ - | -// | Jon Parise | -// | Damian Alejandro Fernandez Sosa | -// +----------------------------------------------------------------------+ -// -// $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 - * @author Jon Parise - * @author Damian Alejandro Fernandez Sosa - * - * @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('/(?_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 @@ - - * @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 - * @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 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ // 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; }