Import Ruty

This commit is contained in:
2024-03-11 00:57:00 +01:00
parent 50481b23df
commit 34a31bb184
617 changed files with 106612 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Client implementation of various SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('PEAR.php');
class Auth_SASL
{
/**
* Factory class. Returns an object of the request
* type.
*
* @param string $type One of: Anonymous
* Plain
* CramMD5
* DigestMD5
* SCRAM-* (any mechanism of the SCRAM family)
* Types are not case sensitive
*/
public static function factory($type)
{
switch (strtolower($type)) {
case 'anonymous':
$filename = 'Auth/SASL/Anonymous.php';
$classname = 'Auth_SASL_Anonymous';
break;
case 'login':
$filename = 'Auth/SASL/Login.php';
$classname = 'Auth_SASL_Login';
break;
case 'plain':
$filename = 'Auth/SASL/Plain.php';
$classname = 'Auth_SASL_Plain';
break;
case 'external':
$filename = 'Auth/SASL/External.php';
$classname = 'Auth_SASL_External';
break;
case 'crammd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: CRAM-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'cram-md5':
$filename = 'Auth/SASL/CramMD5.php';
$classname = 'Auth_SASL_CramMD5';
break;
case 'digestmd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: DIGEST-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'digest-md5':
// $msg = 'DIGEST-MD5 is a deprecated SASL mechanism as per RFC-6331. Using it could be a security risk.';
// trigger_error($msg, E_USER_NOTICE);
$filename = 'Auth/SASL/DigestMD5.php';
$classname = 'Auth_SASL_DigestMD5';
break;
default:
$scram = '/^SCRAM-(.{1,9})$/i';
if (preg_match($scram, $type, $matches))
{
$hash = $matches[1];
$filename = dirname(__FILE__) .'/SASL/SCRAM.php';
$classname = 'Auth_SASL_SCRAM';
$parameter = $hash;
break;
}
return PEAR::raiseError('Invalid SASL mechanism type');
break;
}
require_once($filename);
if (isset($parameter))
$obj = new $classname($parameter);
else
$obj = new $classname();
return $obj;
}
}
?>
@@ -0,0 +1,71 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Anonymous extends Auth_SASL_Common
{
/**
* Not much to do here except return the token supplied.
* No encoding, hashing or encryption takes place for this
* mechanism, simply one of:
* o An email address
* o An opaque string not containing "@" that can be interpreted
* by the sysadmin
* o Nothing
*
* We could have some logic here for the second option, but this
* would by no means create something interpretable.
*
* @param string $token Optional email address or string to provide
* as trace information.
* @return string The unaltered input token
*/
function getResponse($token = '')
{
return $token;
}
}
?>
+105
View File
@@ -0,0 +1,105 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Common functionality to SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
class Auth_SASL_Common
{
/**
* Function which implements HMAC MD5 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
*
* @return string The HMAC-MD5 digest
*/
function _HMAC_MD5($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = pack('H32', md5($key));
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H32', md5($k_ipad . $data));
$digest = md5($k_opad . $inner, $raw_output);
return $digest;
}
/**
* Function which implements HMAC-SHA-1 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
* @return string The HMAC-SHA-1 digest
* @author Jehan <jehan.marmottard@gmail.com>
* @access protected
*/
protected function _HMAC_SHA1($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = sha1($key, TRUE);
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H40', sha1($k_ipad . $data));
$digest = sha1($k_opad . $inner, $raw_output);
return $digest;
}
}
?>
+68
View File
@@ -0,0 +1,68 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
/**
* Implements the CRAM-MD5 SASL mechanism
* This DOES NOT base64 encode the return value,
* you will need to do that yourself.
*
* @param string $user Username
* @param string $pass Password
* @param string $challenge The challenge supplied by the server.
* this should be already base64_decoded.
*
* @return string The string to pass back to the server, of the form
* "<user> <digest>". This is NOT base64_encoded.
*/
function getResponse($user, $pass, $challenge)
{
return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
}
}
?>
+197
View File
@@ -0,0 +1,197 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of DIGEST-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
{
/**
* Provides the (main) client response for DIGEST-MD5
* requires a few extra parameters than the other
* mechanisms, which are unavoidable.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The digest challenge sent by the server
* @param string $hostname The hostname of the machine you're connecting to
* @param string $service The servicename (eg. imap, pop, acap etc)
* @param string $authzid Authorization id (username to proxy as)
* @return string The digest response (NOT base64 encoded)
* @access public
*/
function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
{
$challenge = $this->_parseChallenge($challenge);
$authzid_string = '';
if ($authzid != '') {
$authzid_string = ',authzid="' . $authzid . '"';
}
if (!empty($challenge)) {
$cnonce = $this->_getCnonce();
$digest_uri = sprintf('%s/%s', $service, $hostname);
$response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
if ($challenge['realm']) {
return sprintf('username="%s",realm="%s"' . $authzid_string .
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
} else {
return sprintf('username="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
}
} else {
return PEAR::raiseError('Invalid digest challenge');
}
}
/**
* Parses and verifies the digest challenge*
*
* @param string $challenge The digest challenge
* @return array The parsed challenge as an assoc
* array in the form "directive => value".
* @access private
*/
function _parseChallenge($challenge)
{
$tokens = array();
while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
// Ignore these as per rfc2831
if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
$challenge = substr($challenge, strlen($matches[0]) + 1);
continue;
}
// Allowed multiple "realm" and "auth-param"
if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
if (is_array($tokens[$matches[1]])) {
$tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
} else {
$tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
}
// Any other multiple instance = failure
} elseif (!empty($tokens[$matches[1]])) {
$tokens = array();
break;
} else {
$tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
}
// Remove the just parsed directive from the challenge
$challenge = substr($challenge, strlen($matches[0]) + 1);
}
/**
* Defaults and required directives
*/
// Realm
if (empty($tokens['realm'])) {
$tokens['realm'] = "";
}
// Maxbuf
if (empty($tokens['maxbuf'])) {
$tokens['maxbuf'] = 65536;
}
// Required: nonce, algorithm
if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
return array();
}
return $tokens;
}
/**
* Creates the response= part of the digest response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $realm Realm as provided by the server
* @param string $nonce Nonce as provided by the server
* @param string $cnonce Client nonce
* @param string $digest_uri The digest-uri= value part of the response
* @param string $authzid Authorization id
* @return string The response= part of the digest response
* @access private
*/
function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
{
if ($authzid == '') {
$A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
} else {
$A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
}
$A2 = 'AUTHENTICATE:' . $digest_uri;
return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
}
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
*/
function _getCnonce()
{
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
return base64_encode(fread($fd, 32));
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
return base64_encode(fread($fd, 32));
} else {
$str = '';
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>
+63
View File
@@ -0,0 +1,63 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2008 Christoph Schulz |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Christoph Schulz <develop@kristov.de> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of EXTERNAL SASL mechanism
*
* @author Christoph Schulz <develop@kristov.de>
* @access public
* @version 1.0.3
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_External extends Auth_SASL_Common
{
/**
* Returns EXTERNAL response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string EXTERNAL Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid;
}
}
?>
+65
View File
@@ -0,0 +1,65 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* This is technically not a SASL mechanism, however
* it's used by Net_Sieve, Net_Cyrus and potentially
* other protocols , so here is a good place to abstract
* it.
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Login extends Auth_SASL_Common
{
/**
* Pseudo SASL LOGIN mechanism
*
* @param string $user Username
* @param string $pass Password
* @return string LOGIN string
*/
function getResponse($user, $pass)
{
return sprintf('LOGIN %s %s', $user, $pass);
}
}
?>
+63
View File
@@ -0,0 +1,63 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of PLAIN SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Plain extends Auth_SASL_Common
{
/**
* Returns PLAIN response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string PLAIN Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid . chr(0) . $authcid . chr(0) . $pass;
}
}
?>
+306
View File
@@ -0,0 +1,306 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2011 Jehan |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Jehan <jehan.marmottard@gmail.com |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implementation of SCRAM-* SASL mechanisms.
* SCRAM mechanisms have 3 main steps (initial response, response to the server challenge, then server signature
* verification) which keep state-awareness. Therefore a single class instanciation must be done and reused for the whole
* authentication process.
*
* @author Jehan <jehan.marmottard@gmail.com>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_SCRAM extends Auth_SASL_Common
{
/**
* Construct a SCRAM-H client where 'H' is a cryptographic hash function.
*
* @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual
* Names" registry.
* @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual
* Names"
* format of core PHP hash function.
* @access public
*/
function __construct($hash)
{
// Though I could be strict, I will actually also accept the naming used in the PHP core hash framework.
// For instance "sha1" is accepted, while the registered hash name should be "SHA-1".
$hash = strtolower($hash);
$hashes = array('md2' => 'md2',
'md5' => 'md5',
'sha-1' => 'sha1',
'sha1' => 'sha1',
'sha-224' > 'sha224',
'sha224' > 'sha224',
'sha-256' => 'sha256',
'sha256' => 'sha256',
'sha-384' => 'sha384',
'sha384' => 'sha384',
'sha-512' => 'sha512',
'sha512' => 'sha512');
if (function_exists('hash_hmac') && isset($hashes[$hash]))
{
$this->hash = create_function('$data', 'return hash("' . $hashes[$hash] . '", $data, TRUE);');
$this->hmac = create_function('$key,$str,$raw', 'return hash_hmac("' . $hashes[$hash] . '", $str, $key, $raw);');
}
elseif ($hash == 'md5')
{
$this->hash = create_function('$data', 'return md5($data, true);');
$this->hmac = array($this, '_HMAC_MD5');
}
elseif (in_array($hash, array('sha1', 'sha-1')))
{
$this->hash = create_function('$data', 'return sha1($data, true);');
$this->hmac = array($this, '_HMAC_SHA1');
}
else
return PEAR::raiseError('Invalid SASL mechanism type');
}
/**
* Provides the (main) client response for SCRAM-H.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The challenge sent by the server.
* If the challenge is NULL or an empty string, the result will be the "initial response".
* @param string $authzid Authorization id (username to proxy as)
* @return string|false The response (binary, NOT base64 encoded)
* @access public
*/
public function getResponse($authcid, $pass, $challenge = NULL, $authzid = NULL)
{
$authcid = $this->_formatName($authcid);
if (empty($authcid))
{
return false;
}
if (!empty($authzid))
{
$authzid = $this->_formatName($authzid);
if (empty($authzid))
{
return false;
}
}
if (empty($challenge))
{
return $this->_generateInitialResponse($authcid, $authzid);
}
else
{
return $this->_generateResponse($challenge, $pass);
}
}
/**
* Prepare a name for inclusion in a SCRAM response.
*
* @param string $username a name to be prepared.
* @return string the reformated name.
* @access private
*/
private function _formatName($username)
{
// TODO: prepare through the SASLprep profile of the stringprep algorithm.
// See RFC-4013.
$username = str_replace('=', '=3D', $username);
$username = str_replace(',', '=2C', $username);
return $username;
}
/**
* Generate the initial response which can be either sent directly in the first message or as a response to an empty
* server challenge.
*
* @param string $authcid Prepared authentication identity.
* @param string $authzid Prepared authorization identity.
* @return string The SCRAM response to send.
* @access private
*/
private function _generateInitialResponse($authcid, $authzid)
{
$init_rep = '';
$gs2_cbind_flag = 'n,'; // TODO: support channel binding.
$this->gs2_header = $gs2_cbind_flag . (!empty($authzid)? 'a=' . $authzid : '') . ',';
// I must generate a client nonce and "save" it for later comparison on second response.
$this->cnonce = $this->_getCnonce();
// XXX: in the future, when mandatory and/or optional extensions are defined in any updated RFC,
// this message can be updated.
$this->first_message_bare = 'n=' . $authcid . ',r=' . $this->cnonce;
return $this->gs2_header . $this->first_message_bare;
}
/**
* Parses and verifies a non-empty SCRAM challenge.
*
* @param string $challenge The SCRAM challenge
* @return string|false The response to send; false in case of wrong challenge or if an initial response has not
* been generated first.
* @access private
*/
private function _generateResponse($challenge, $password)
{
// XXX: as I don't support mandatory extension, I would fail on them.
// And I simply ignore any optional extension.
$server_message_regexp = "#^r=([\x21-\x2B\x2D-\x7E]+),s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?),i=([0-9]*)(,[A-Za-z]=[^,])*$#";
if (!isset($this->cnonce, $this->gs2_header)
|| !preg_match($server_message_regexp, $challenge, $matches))
{
return false;
}
$nonce = $matches[1];
$salt = base64_decode($matches[2]);
if (!$salt)
{
// Invalid Base64.
return false;
}
$i = intval($matches[3]);
$cnonce = substr($nonce, 0, strlen($this->cnonce));
if ($cnonce <> $this->cnonce)
{
// Invalid challenge! Are we under attack?
return false;
}
$channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding.
$final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension.
// TODO: $password = $this->normalize($password); // SASLprep profile of stringprep.
$saltedPassword = $this->hi($password, $salt, $i);
$this->saltedPassword = $saltedPassword;
$clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE);
$storedKey = call_user_func($this->hash, $clientKey, TRUE);
$authMessage = $this->first_message_bare . ',' . $challenge . ',' . $final_message;
$this->authMessage = $authMessage;
$clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, TRUE);
$clientProof = $clientKey ^ $clientSignature;
$proof = ',p=' . base64_encode($clientProof);
return $final_message . $proof;
}
/**
* SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
* absolutely be checked against this function. If this fails, the entity which we are communicating with is probably
* not the server as it has not access to your ServerKey.
*
* @param string $data The additional data sent along a successful outcome.
* @return bool Whether the server has been authenticated.
* If false, the client must close the connection and consider to be under a MITM attack.
* @access public
*/
public function processOutcome($data)
{
$verifier_regexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?)$#';
if (!isset($this->saltedPassword, $this->authMessage)
|| !preg_match($verifier_regexp, $data, $matches))
{
// This cannot be an outcome, you never sent the challenge's response.
return false;
}
$verifier = $matches[1];
$proposed_serverSignature = base64_decode($verifier);
$serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);
$serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, TRUE);
return ($proposed_serverSignature === $serverSignature);
}
/**
* Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function.
*
* @param string $str The string to hash.
* @param string $hash The hash value.
* @param int $i The iteration count.
* @access private
*/
private function hi($str, $salt, $i)
{
$int1 = "\0\0\0\1";
$ui = call_user_func($this->hmac, $str, $salt . $int1, true);
$result = $ui;
for ($k = 1; $k < $i; $k++)
{
$ui = call_user_func($this->hmac, $str, $ui, true);
$result = $result ^ $ui;
}
return $result;
}
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
* @author Richard Heyes <richard@php.net>
*/
private function _getCnonce()
{
// TODO: I reused the nonce function from the DigestMD5 class.
// I should probably make this a protected function in Common.
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
return base64_encode(fread($fd, 32));
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
return base64_encode(fread($fd, 32));
} else {
$str = '';
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>
+46
View File
@@ -0,0 +1,46 @@
# Auth_SASL - Abstraction of various SASL mechanism responses
[![Build Status](https://travis-ci.org/pear/Auth_SASL.svg?branch=master)](https://travis-ci.org/pear/Auth_SASL)
Provides code to generate responses to common SASL mechanisms, including:
- Digest-MD5
- Cram-MD5
- Plain
- Anonymous
- Login (Pseudo mechanism)
- SCRAM
[Homepage](http://pear.php.net/package/Auth_SASL/)
## Installation
For a PEAR installation that downloads from the PEAR channel:
`$ pear install pear/auth_sasl`
For a PEAR installation from a previously downloaded tarball:
`$ pear install Auth_SASL-*.tgz`
For a PEAR installation from a code clone:
`$ pear install package.xml`
For a local composer installation:
`$ composer install`
To add as a dependency to your composer-managed application:
`$composer require pear/auth_sasl`
## Tests
Run the tests from a local composer installation:
`$ ./vendor/bin/phpunit`
## License
BSD license
+41
View File
@@ -0,0 +1,41 @@
{
"authors": [
{
"email": "amistry@am-productions.biz",
"name": "Anish Mistry",
"role": "Lead"
},
{
"email": "richard@php.net",
"name": "Richard Heyes",
"role": "Lead"
},
{
"email": "michael@bretterklieber.com",
"name": "Michael Bretterklieber",
"role": "Lead"
}
],
"autoload": {
"psr-0": {
"Auth": "./"
}
},
"description": "Abstraction of various SASL mechanism responses",
"include-path": [
"./"
],
"license": "BSD",
"name": "pear/auth_sasl",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Auth_SASL",
"source": "https://github.com/pear/Auth_SASL"
},
"type": "library",
"require": {
"pear/pear_exception": "@stable"
},
"require-dev": {
"phpunit/phpunit": "@stable"
}
}
+217
View File
@@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<package version="2.0"
xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd"
>
<name>Auth_SASL</name>
<channel>pear.php.net</channel>
<summary>Abstraction of various SASL mechanism responses</summary>
<description>
Provides code to generate responses to common SASL mechanisms, including:
- Digest-MD5
- Cram-MD5
- Plain
- Anonymous
- Login (Pseudo mechanism)
- SCRAM
</description>
<lead>
<name>Anish Mistry</name>
<user>amistry</user>
<email>amistry@am-productions.biz</email>
<active>no</active>
</lead>
<lead>
<name>Richard Heyes</name>
<user>richard</user>
<email>richard@php.net</email>
<active>no</active>
</lead>
<lead>
<name>Michael Bretterklieber</name>
<user>mbretter</user>
<email>michael@bretterklieber.com</email>
<active>no</active>
</lead>
<date>2017-03-07</date>
<version>
<release>1.1.0</release>
<api>1.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
* Set minimum PHP version to 5.4.0
* Set minimum PEAR version to 1.10.1
* Request #21033: PHP warning depreciated
</notes>
<contents>
<dir name="/">
<dir name="Auth">
<dir name="SASL">
<file name="Anonymous.php" role="php" />
<file name="Common.php" role="php" />
<file name="CramMD5.php" role="php" />
<file name="DigestMD5.php" role="php" />
<file name="External.php" role="php" />
<file name="Login.php" role="php" />
<file name="Plain.php" role="php" />
<file name="SCRAM.php" role="php" />
</dir> <!-- //SASL -->
<file name="SASL.php" role="php" />
</dir><!-- /Auth -->
</dir> <!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.4.0</min>
</php>
<pearinstaller>
<min>1.10.1</min>
</pearinstaller>
</required>
</dependencies>
<phprelease />
<changelog>
<release>
<version>
<release>1.1.0</release>
<api>1.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2017-03-07</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
* Set minimum PHP version to 5.4.0
* Set minimum PEAR version to 1.10.1
* Request #21033: PHP warning depreciated
</notes>
</release>
<release>
<version>
<release>1.0.6</release>
<api>1.0.3</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2011-09-27</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
QA release
* Bug #18856: Authentication warnings because of wrong Auth_SASL::factory argument [kguest]
</notes>
</release>
<release>
<version>
<release>1.0.5</release>
<api>1.0.3</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2011-09-04</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
QA release
* Added support for any mechanism of the SCRAM family; with thanks to Jehan Pagès. [kguest]
* crammd5 and digestmd5 mechanisms name deprecated in favour of IANA registered names 'cram-md5' and 'digest-md5'; with thanks to Jehan Pagès. [kguest]
</notes>
</release>
<release>
<version>
<release>1.0.4</release>
<api>1.0.3</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2010-02-07</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
QA release
* Fix bug #16624: open_basedir restriction warning in DigestMD5.php [till]
</notes>
</release>
<release>
<version>
<release>1.0.3</release>
<api>1.0.3</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2009-08-05</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
QA release
* Move SVN to proper directory structure [cweiske]
* Fix Bug #8775: Error in package.xml
* Fix Bug #14671: Security issue due to seeding random number generator [cweiske]
</notes>
</release>
<release>
<version>
<release>1.0.2</release>
<api>1.0.2</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2006-05-21</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
* Fixed Bug #2143 Auth_SASL_DigestMD5::getResponse() generates invalid response
* Fixed Bug #6611 Suppress PHP 5 Notice Errors
* Fixed Bug #2154 realm isn't contained in challange
</notes>
</release>
<release>
<version>
<release>1.0.1</release>
<api>1.0.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2003-09-11</date>
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
<notes>* Added authcid/authzid separation in PLAIN and DIGEST-MD5.
</notes>
</release>
</changelog>
</package>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,142 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Class that represent an option action.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
abstract class Console_CommandLine_Action
{
// Properties {{{
/**
* A reference to the result instance.
*
* @var Console_CommandLine_Result $result The result instance
*/
protected $result;
/**
* A reference to the option instance.
*
* @var Console_CommandLine_Option $option The action option
*/
protected $option;
/**
* A reference to the parser instance.
*
* @var Console_CommandLine $parser The parser
*/
protected $parser;
// }}}
// __construct() {{{
/**
* Constructor
*
* @param Console_CommandLine_Result $result The result instance
* @param Console_CommandLine_Option $option The action option
* @param Console_CommandLine $parser The current parser
*
* @return void
*/
public function __construct($result, $option, $parser)
{
$this->result = $result;
$this->option = $option;
$this->parser = $parser;
}
// }}}
// getResult() {{{
/**
* Convenience method to retrieve the value of result->options[name].
*
* @return mixed The result value or null
*/
public function getResult()
{
if (isset($this->result->options[$this->option->name])) {
return $this->result->options[$this->option->name];
}
return null;
}
// }}}
// format() {{{
/**
* Allow a value to be pre-formatted prior to being used in a choices test.
* Setting $value to the new format will keep the formatting.
*
* @param mixed &$value The value to format
*
* @return mixed The formatted value
*/
public function format(&$value)
{
return $value;
}
// }}}
// setResult() {{{
/**
* Convenience method to assign the result->options[name] value.
*
* @param mixed $result The result value
*
* @return void
*/
public function setResult($result)
{
$this->result->options[$this->option->name] = $result;
}
// }}}
// execute() {{{
/**
* Executes the action with the value entered by the user.
* All children actions must implement this method.
*
* @param mixed $value The option value
* @param array $params An optional array of parameters
*
* @return string
*/
abstract public function execute($value = false, $params = array());
// }}}
}
@@ -0,0 +1,80 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the Callback action.
*
* The result option array entry value is set to the return value of the
* callback defined in the option.
*
* There are two steps to defining a callback option:
* - define the option itself using the callback action
* - write the callback; this is a function (or method) that takes five
* arguments, as described below.
*
* All callbacks are called as follows:
* <code>
* callable_func(
* $value, // the value of the option
* $option_instance, // the option instance
* $result_instance, // the result instance
* $parser_instance, // the parser instance
* $params // an array of params as specified in the option
* );
* </code>
* and *must* return the option value.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_Callback extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The value of the option
* @param array $params An optional array of parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$this->setResult(call_user_func($this->option->callback, $value,
$this->option, $this->result, $this->parser, $params));
}
// }}}
}
@@ -0,0 +1,86 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the Version action.
*
* The execute methode add 1 to the value of the result option array entry.
* The value is incremented each time the option is found, for example
* with an option defined like that:
*
* <code>
* $parser->addOption(
* 'verbose',
* array(
* 'short_name' => '-v',
* 'action' => 'Counter'
* )
* );
* </code>
* If the user type:
* <code>
* $ script.php -v -v -v
* </code>
* or:
* <code>
* $ script.php -vvv
* </code>
* the verbose variable will be set to to 3.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_Counter extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An optional array of parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$result = $this->getResult();
if ($result === null) {
$result = 0;
}
$this->setResult(++$result);
}
// }}}
}
@@ -0,0 +1,60 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the Help action, a special action that displays the
* help message, telling the user how to use the program.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_Help extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An optional array of parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
return $this->parser->displayUsage();
}
// }}}
}
@@ -0,0 +1,73 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the List action, a special action that simply output an
* array as a list.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_List extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
* Possible parameters are:
* - message: an alternative message to display instead of the default
* message,
* - delimiter: an alternative delimiter instead of the comma,
* - post: a string to append after the message (default is the new line
* char).
*
* @param mixed $value The option value
* @param array $params An optional array of parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$list = isset($params['list']) ? $params['list'] : array();
$msg = isset($params['message'])
? $params['message']
: $this->parser->message_provider->get('LIST_DISPLAYED_MESSAGE');
$del = isset($params['delimiter']) ? $params['delimiter'] : ', ';
$post = isset($params['post']) ? $params['post'] : "\n";
$this->parser->outputter->stdout($msg . implode($del, $list) . $post);
exit(0);
}
// }}}
}
@@ -0,0 +1,90 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the Password action, a special action that allow the
* user to specify the password on the commandline or to be prompted for
* entering it.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_Password extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$this->setResult(empty($value) ? $this->_promptPassword() : $value);
}
// }}}
// _promptPassword() {{{
/**
* Prompts the password to the user without echoing it.
*
* @return string
* @todo not echo-ing the password does not work on windows is there a way
* to make this work ?
*/
private function _promptPassword()
{
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
fwrite(STDOUT,
$this->parser->message_provider->get('PASSWORD_PROMPT_ECHO'));
@flock(STDIN, LOCK_EX);
$passwd = fgets(STDIN);
@flock(STDIN, LOCK_UN);
} else {
fwrite(STDOUT, $this->parser->message_provider->get('PASSWORD_PROMPT'));
// disable echoing
system('stty -echo');
@flock(STDIN, LOCK_EX);
$passwd = fgets(STDIN);
@flock(STDIN, LOCK_UN);
system('stty echo');
}
return trim($passwd);
}
// }}}
}
@@ -0,0 +1,78 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreArray action.
*
* The execute method appends the value of the option entered by the user to
* the result option array entry.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreArray extends Console_CommandLine_Action
{
// Protected properties {{{
/**
* Force a clean result when first called, overriding any defaults assigned.
*
* @var object $firstPass First time this action has been called.
*/
protected $firstPass = true;
// }}}
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An optional array of parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$result = $this->getResult();
if (null === $result || $this->firstPass) {
$result = array();
$this->firstPass = false;
}
$result[] = $value;
$this->setResult($result);
}
// }}}
}
@@ -0,0 +1,64 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreFalse action.
*
* The execute method store the boolean 'false' in the corrsponding result
* option array entry (the value is true if the option is not present in the
* command line entered by the user).
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreFalse extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$this->setResult(false);
}
// }}}
}
@@ -0,0 +1,76 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreFloat action.
*
* The execute method store the value of the option entered by the user as a
* float in the result option array entry, if the value passed is not a float
* an Exception is raised.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreFloat extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
* @throws Console_CommandLine_Exception
*/
public function execute($value = false, $params = array())
{
if (!is_numeric($value)) {
include_once 'Console/CommandLine/Exception.php';
throw Console_CommandLine_Exception::factory(
'OPTION_VALUE_TYPE_ERROR',
array(
'name' => $this->option->name,
'type' => 'float',
'value' => $value
),
$this->parser
);
}
$this->setResult((float)$value);
}
// }}}
}
@@ -0,0 +1,76 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreInt action.
*
* The execute method store the value of the option entered by the user as an
* integer in the result option array entry, if the value passed is not an
* integer an Exception is raised.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreInt extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
* @throws Console_CommandLine_Exception
*/
public function execute($value = false, $params = array())
{
if (!is_numeric($value)) {
include_once 'Console/CommandLine/Exception.php';
throw Console_CommandLine_Exception::factory(
'OPTION_VALUE_TYPE_ERROR',
array(
'name' => $this->option->name,
'type' => 'int',
'value' => $value
),
$this->parser
);
}
$this->setResult((int)$value);
}
// }}}
}
@@ -0,0 +1,62 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreString action.
*
* The execute method store the value of the option entered by the user as a
* string in the result option array entry.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreString extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$this->setResult((string)$value);
}
// }}}
}
@@ -0,0 +1,63 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the StoreTrue action.
*
* The execute method store the boolean 'true' in the corrsponding result
* option array entry (the value is false if the option is not present in the
* command line entered by the user).
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_StoreTrue extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
$this->setResult(true);
}
// }}}
}
@@ -0,0 +1,60 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine/Action.php';
/**
* Class that represent the Version action, a special action that displays the
* version string of the program.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Action_Version extends Console_CommandLine_Action
{
// execute() {{{
/**
* Executes the action with the value entered by the user.
*
* @param mixed $value The option value
* @param array $params An array of optional parameters
*
* @return string
*/
public function execute($value = false, $params = array())
{
return $this->parser->displayVersion();
}
// }}}
}
@@ -0,0 +1,102 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Include base element class.
*/
require_once 'Console/CommandLine/Element.php';
/**
* Class that represent a command line argument.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Argument extends Console_CommandLine_Element
{
// Public properties {{{
/**
* Setting this to true will tell the parser that the argument expects more
* than one argument and that argument values should be stored in an array.
*
* @var boolean $multiple Whether the argument expects multiple values
*/
public $multiple = false;
/**
* Setting this to true will tell the parser that the argument is optional
* and can be ommited.
* Note that it is not a good practice to make arguments optional, it is
* the role of the options to be optional, by essence.
*
* @var boolean $optional Whether the argument is optional or not.
*/
public $optional = false;
/**
* An array of possible values for the argument.
*
* @var array $choices Valid choices for the argument
*/
public $choices = array();
// }}}
// validate() {{{
/**
* Validates the argument instance.
*
* @return void
* @throws Console_CommandLine_Exception
* @todo use exceptions
*/
public function validate()
{
// check if the argument name is valid
if (!preg_match('/^[a-zA-Z_\x7f-\xff]+[a-zA-Z0-9_\x7f-\xff]*$/',
$this->name)) {
Console_CommandLine::triggerError(
'argument_bad_name',
E_USER_ERROR,
array('{$name}' => $this->name)
);
}
if (!$this->optional && $this->default !== null) {
Console_CommandLine::triggerError(
'argument_no_default',
E_USER_ERROR
);
}
parent::validate();
}
// }}}
}
@@ -0,0 +1,76 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* File containing the parent class.
*/
require_once 'Console/CommandLine.php';
/**
* Class that represent a command with option and arguments.
*
* This class exist just to clarify the interface but at the moment it is
* strictly identical to Console_CommandLine class, it could change in the
* future though.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Command extends Console_CommandLine
{
// Public properties {{{
/**
* An array of aliases for the subcommand.
*
* @var array $aliases Aliases for the subcommand.
*/
public $aliases = array();
// }}}
// __construct() {{{
/**
* Constructor.
*
* @param array $params An optional array of parameters
*
* @return void
*/
public function __construct($params = array())
{
if (isset($params['aliases'])) {
$this->aliases = $params['aliases'];
}
parent::__construct($params);
}
// }}}
}
@@ -0,0 +1,66 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2007 David JEAN LOUIS, 2009 silverorange
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 1.1.0
* @filesource
*/
/**
* Common interfacefor message providers that allow overriding with custom
* messages
*
* Message providers may optionally implement this interface.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2007 David JEAN LOUIS, 2009 silverorange
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Interface available since release 1.1.0
*/
interface Console_CommandLine_CustomMessageProvider
{
// getWithCustomMesssages() {{{
/**
* Retrieves the given string identifier corresponding message.
*
* For a list of identifiers please see the provided default message
* provider.
*
* @param string $code The string identifier of the message
* @param array $vars An array of template variables
* @param array $messages An optional array of messages to use. Array
* indexes are message codes.
*
* @return string
* @see Console_CommandLine_MessageProvider
* @see Console_CommandLine_MessageProvider_Default
*/
public function getWithCustomMessages(
$code, $vars = array(), $messages = array()
);
// }}}
}
@@ -0,0 +1,151 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Class that represent a command line element (an option, or an argument).
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
abstract class Console_CommandLine_Element
{
// Public properties {{{
/**
* The element name.
*
* @var string $name Element name
*/
public $name;
/**
* The name of variable displayed in the usage message, if no set it
* defaults to the "name" property.
*
* @var string $help_name Element "help" variable name
*/
public $help_name;
/**
* The element description.
*
* @var string $description Element description
*/
public $description;
/**
* The default value of the element if not provided on the command line.
*
* @var mixed $default Default value of the option.
*/
public $default;
/**
* Custom errors messages for this element
*
* This array is of the form:
* <code>
* <?php
* array(
* $messageName => $messageText,
* $messageName => $messageText,
* ...
* );
* ?>
* </code>
*
* If specified, these messages override the messages provided by the
* default message provider. For example:
* <code>
* <?php
* $messages = array(
* 'ARGUMENT_REQUIRED' => 'The argument foo is required.',
* );
* ?>
* </code>
*
* @var array
* @see Console_CommandLine_MessageProvider_Default
*/
public $messages = array();
// }}}
// __construct() {{{
/**
* Constructor.
*
* @param string $name The name of the element
* @param array $params An optional array of parameters
*
* @return void
*/
public function __construct($name = null, $params = array())
{
$this->name = $name;
foreach ($params as $attr => $value) {
if (property_exists($this, $attr)) {
$this->$attr = $value;
}
}
}
// }}}
// toString() {{{
/**
* Returns the string representation of the element.
*
* @return string The string representation of the element
* @todo use __toString() instead
*/
public function toString()
{
return $this->help_name;
}
// }}}
// validate() {{{
/**
* Validates the element instance and set it's default values.
*
* @return void
* @throws Console_CommandLine_Exception
*/
public function validate()
{
// if no help_name passed, default to name
if ($this->help_name == null) {
$this->help_name = $this->name;
}
}
// }}}
}
@@ -0,0 +1,97 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Include the PEAR_Exception class
*/
require_once 'PEAR/Exception.php';
/**
* Interface for custom message provider.
*/
require_once 'Console/CommandLine/CustomMessageProvider.php';
/**
* Class for exceptions raised by the Console_CommandLine package.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Exception extends PEAR_Exception
{
// Codes constants {{{
/**#@+
* Exception code constants.
*/
const OPTION_VALUE_REQUIRED = 1;
const OPTION_VALUE_UNEXPECTED = 2;
const OPTION_VALUE_TYPE_ERROR = 3;
const OPTION_UNKNOWN = 4;
const ARGUMENT_REQUIRED = 5;
const INVALID_SUBCOMMAND = 6;
/**#@-*/
// }}}
// factory() {{{
/**
* Convenience method that builds the exception with the array of params by
* calling the message provider class.
*
* @param string $code The string identifier of the
* exception.
* @param array $params Array of template vars/values
* @param Console_CommandLine $parser An instance of the parser
* @param array $messages An optional array of messages
* passed to the message provider.
*
* @return object an instance of Console_CommandLine_Exception
*/
public static function factory(
$code, $params, $parser, array $messages = array()
) {
$provider = $parser->message_provider;
if ($provider instanceof Console_CommandLine_CustomMessageProvider) {
$msg = $provider->getWithCustomMessages(
$code,
$params,
$messages
);
} else {
$msg = $provider->get($code, $params);
}
$const = 'Console_CommandLine_Exception::' . $code;
$code = defined($const) ? constant($const) : 0;
return new Console_CommandLine_Exception($msg, $code);
}
// }}}
}
@@ -0,0 +1,56 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Message providers common interface, all message providers must implement
* this interface.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
interface Console_CommandLine_MessageProvider
{
// get() {{{
/**
* Retrieves the given string identifier corresponding message.
* For a list of identifiers please see the provided default message
* provider.
*
* @param string $code The string identifier of the message
* @param array $vars An array of template variables
*
* @return string
* @see Console_CommandLine_MessageProvider_Default
*/
public function get($code, $vars=array());
// }}}
}
@@ -0,0 +1,153 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* The message provider interface.
*/
require_once 'Console/CommandLine/MessageProvider.php';
/**
* The custom message provider interface.
*/
require_once 'Console/CommandLine/CustomMessageProvider.php';
/**
* Lightweight class that manages messages used by Console_CommandLine package,
* allowing the developper to customize these messages, for example to
* internationalize a command line frontend.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_MessageProvider_Default
implements Console_CommandLine_MessageProvider,
Console_CommandLine_CustomMessageProvider
{
// Properties {{{
/**
* Associative array of messages
*
* @var array $messages
*/
protected $messages = array(
'OPTION_VALUE_REQUIRED' => 'Option "{$name}" requires a value.',
'OPTION_VALUE_UNEXPECTED' => 'Option "{$name}" does not expect a value (got "{$value}").',
'OPTION_VALUE_NOT_VALID' => 'Option "{$name}" must be one of the following: "{$choices}" (got "{$value}").',
'ARGUMENT_VALUE_NOT_VALID'=> 'Argument "{$name}" must be one of the following: "{$choices}" (got "{$value}").',
'OPTION_VALUE_TYPE_ERROR' => 'Option "{$name}" requires a value of type {$type} (got "{$value}").',
'OPTION_AMBIGUOUS' => 'Ambiguous option "{$name}", can be one of the following: {$matches}.',
'OPTION_UNKNOWN' => 'Unknown option "{$name}".',
'ARGUMENT_REQUIRED' => 'You must provide at least {$argnum} argument{$plural}.',
'PROG_HELP_LINE' => 'Type "{$progname} --help" to get help.',
'PROG_VERSION_LINE' => '{$progname} version {$version}.',
'COMMAND_HELP_LINE' => 'Type "{$progname} <command> --help" to get help on specific command.',
'USAGE_WORD' => 'Usage',
'OPTION_WORD' => 'Options',
'ARGUMENT_WORD' => 'Arguments',
'COMMAND_WORD' => 'Commands',
'PASSWORD_PROMPT' => 'Password: ',
'PASSWORD_PROMPT_ECHO' => 'Password (warning: will echo): ',
'INVALID_CUSTOM_INSTANCE' => 'Instance does not implement the required interface',
'LIST_OPTION_MESSAGE' => 'lists valid choices for option {$name}',
'LIST_DISPLAYED_MESSAGE' => 'Valid choices are: ',
'INVALID_SUBCOMMAND' => 'Command "{$command}" is not valid.',
'SUBCOMMAND_REQUIRED' => 'Please enter one of the following command: {$commands}.',
);
// }}}
// get() {{{
/**
* Retrieve the given string identifier corresponding message.
*
* @param string $code The string identifier of the message
* @param array $vars An array of template variables
*
* @return string
*/
public function get($code, $vars = array())
{
if (!isset($this->messages[$code])) {
return 'UNKNOWN';
}
return $this->replaceTemplateVars($this->messages[$code], $vars);
}
// }}}
// getWithCustomMessages() {{{
/**
* Retrieve the given string identifier corresponding message.
*
* @param string $code The string identifier of the message
* @param array $vars An array of template variables
* @param array $messages An optional array of messages to use. Array
* indexes are message codes.
*
* @return string
*/
public function getWithCustomMessages(
$code, $vars = array(), $messages = array()
) {
// get message
if (isset($messages[$code])) {
$message = $messages[$code];
} elseif (isset($this->messages[$code])) {
$message = $this->messages[$code];
} else {
$message = 'UNKNOWN';
}
return $this->replaceTemplateVars($message, $vars);
}
// }}}
// replaceTemplateVars() {{{
/**
* Replaces template vars in a message
*
* @param string $message The message
* @param array $vars An array of template variables
*
* @return string
*/
protected function replaceTemplateVars($message, $vars = array())
{
$tmpkeys = array_keys($vars);
$keys = array();
foreach ($tmpkeys as $key) {
$keys[] = '{$' . $key . '}';
}
return str_replace($keys, array_values($vars), $message);
}
// }}}
}
@@ -0,0 +1,366 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required by this class.
*/
require_once 'Console/CommandLine.php';
require_once 'Console/CommandLine/Element.php';
/**
* Class that represent a commandline option.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Option extends Console_CommandLine_Element
{
// Public properties {{{
/**
* The option short name (ex: -v).
*
* @var string $short_name Short name of the option
*/
public $short_name;
/**
* The option long name (ex: --verbose).
*
* @var string $long_name Long name of the option
*/
public $long_name;
/**
* The option action, defaults to "StoreString".
*
* @var string $action Option action
*/
public $action = 'StoreString';
/**
* An array of possible values for the option. If this array is not empty
* and the value passed is not in the array an exception is raised.
* This only make sense for actions that accept values of course.
*
* @var array $choices Valid choices for the option
*/
public $choices = array();
/**
* The callback function (or method) to call for an action of type
* Callback, this can be any callable supported by the php function
* call_user_func.
*
* Example:
*
* <code>
* $parser->addOption('myoption', array(
* 'short_name' => '-m',
* 'long_name' => '--myoption',
* 'action' => 'Callback',
* 'callback' => 'myCallbackFunction'
* ));
* </code>
*
* @var callable $callback The option callback
*/
public $callback;
/**
* An associative array of additional params to pass to the class
* corresponding to the action, this array will also be passed to the
* callback defined for an action of type Callback, Example:
*
* <code>
* // for a custom action
* $parser->addOption('myoption', array(
* 'short_name' => '-m',
* 'long_name' => '--myoption',
* 'action' => 'MyCustomAction',
* 'action_params' => array('foo'=>true, 'bar'=>false)
* ));
*
* // if the user type:
* // $ <yourprogram> -m spam
* // in your MyCustomAction class the execute() method will be called
* // with the value 'spam' as first parameter and
* // array('foo'=>true, 'bar'=>false) as second parameter
* </code>
*
* @var array $action_params Additional parameters to pass to the action
*/
public $action_params = array();
/**
* For options that expect an argument, this property tells the parser if
* the option argument is optional and can be ommited.
*
* @var bool $argumentOptional Whether the option arg is optional or not
*/
public $argument_optional = false;
/**
* For options that uses the "choice" property only.
* Adds a --list-<choice> option to the parser that displays the list of
* choices for the option.
*
* @var bool $add_list_option Whether to add a list option or not
*/
public $add_list_option = false;
// }}}
// Private properties {{{
/**
* When an action is called remember it to allow for multiple calls.
*
* @var object $action_instance Placeholder for action
*/
private $_action_instance = null;
// }}}
// __construct() {{{
/**
* Constructor.
*
* @param string $name The name of the option
* @param array $params An optional array of parameters
*
* @return void
*/
public function __construct($name = null, $params = array())
{
parent::__construct($name, $params);
if ($this->action == 'Password') {
// special case for Password action, password can be passed to the
// commandline or prompted by the parser
$this->argument_optional = true;
}
}
// }}}
// toString() {{{
/**
* Returns the string representation of the option.
*
* @param string $delim Delimiter to use between short and long option
*
* @return string The string representation of the option
* @todo use __toString() instead
*/
public function toString($delim = ", ")
{
$ret = '';
$padding = '';
if ($this->short_name != null) {
$ret .= $this->short_name;
if ($this->expectsArgument()) {
$ret .= ' ' . $this->help_name;
}
$padding = $delim;
}
if ($this->long_name != null) {
$ret .= $padding . $this->long_name;
if ($this->expectsArgument()) {
$ret .= '=' . $this->help_name;
}
}
return $ret;
}
// }}}
// expectsArgument() {{{
/**
* Returns true if the option requires one or more argument and false
* otherwise.
*
* @return bool Whether the option expects an argument or not
*/
public function expectsArgument()
{
if ($this->action == 'StoreTrue' || $this->action == 'StoreFalse' ||
$this->action == 'Help' || $this->action == 'Version' ||
$this->action == 'Counter' || $this->action == 'List') {
return false;
}
return true;
}
// }}}
// dispatchAction() {{{
/**
* Formats the value $value according to the action of the option and
* updates the passed Console_CommandLine_Result object.
*
* @param mixed $value The value to format
* @param Console_CommandLine_Result $result The result instance
* @param Console_CommandLine $parser The parser instance
*
* @return void
* @throws Console_CommandLine_Exception
*/
public function dispatchAction($value, $result, $parser)
{
$actionInfo = Console_CommandLine::$actions[$this->action];
if (true === $actionInfo[1]) {
// we have a "builtin" action
$tokens = explode('_', $actionInfo[0]);
include_once implode('/', $tokens) . '.php';
}
$clsname = $actionInfo[0];
if ($this->_action_instance === null) {
$this->_action_instance = new $clsname($result, $this, $parser);
}
// check value is in option choices
if (!empty($this->choices) && !in_array($this->_action_instance->format($value), $this->choices)) {
throw Console_CommandLine_Exception::factory(
'OPTION_VALUE_NOT_VALID',
array(
'name' => $this->name,
'choices' => implode('", "', $this->choices),
'value' => $value,
),
$parser,
$this->messages
);
}
$this->_action_instance->execute($value, $this->action_params);
}
// }}}
// validate() {{{
/**
* Validates the option instance.
*
* @return void
* @throws Console_CommandLine_Exception
* @todo use exceptions instead
*/
public function validate()
{
// check if the option name is valid
if (!preg_match('/^[a-zA-Z_\x7f-\xff]+[a-zA-Z0-9_\x7f-\xff]*$/',
$this->name)) {
Console_CommandLine::triggerError('option_bad_name',
E_USER_ERROR, array('{$name}' => $this->name));
}
// call the parent validate method
parent::validate();
// a short_name or a long_name must be provided
if ($this->short_name == null && $this->long_name == null) {
Console_CommandLine::triggerError('option_long_and_short_name_missing',
E_USER_ERROR, array('{$name}' => $this->name));
}
// check if the option short_name is valid
if ($this->short_name != null &&
!(preg_match('/^\-[a-zA-Z]{1}$/', $this->short_name))) {
Console_CommandLine::triggerError('option_bad_short_name',
E_USER_ERROR, array(
'{$name}' => $this->name,
'{$short_name}' => $this->short_name
));
}
// check if the option long_name is valid
if ($this->long_name != null &&
!preg_match('/^\-\-[a-zA-Z]+[a-zA-Z0-9_\-]*$/', $this->long_name)) {
Console_CommandLine::triggerError('option_bad_long_name',
E_USER_ERROR, array(
'{$name}' => $this->name,
'{$long_name}' => $this->long_name
));
}
// check if we have a valid action
if (!is_string($this->action)) {
Console_CommandLine::triggerError('option_bad_action',
E_USER_ERROR, array('{$name}' => $this->name));
}
if (!isset(Console_CommandLine::$actions[$this->action])) {
Console_CommandLine::triggerError('option_unregistered_action',
E_USER_ERROR, array(
'{$action}' => $this->action,
'{$name}' => $this->name
));
}
// if the action is a callback, check that we have a valid callback
if ($this->action == 'Callback' && !is_callable($this->callback)) {
Console_CommandLine::triggerError('option_invalid_callback',
E_USER_ERROR, array('{$name}' => $this->name));
}
}
// }}}
// setDefaults() {{{
/**
* Set the default value according to the configured action.
*
* Note that for backward compatibility issues this method is only called
* when the 'force_options_defaults' is set to true, it will become the
* default behaviour in the next major release of Console_CommandLine.
*
* @return void
*/
public function setDefaults()
{
if ($this->default !== null) {
// already set
return;
}
switch ($this->action) {
case 'Counter':
case 'StoreInt':
$this->default = 0;
break;
case 'StoreFloat':
$this->default = 0.0;
break;
case 'StoreArray':
$this->default = array();
break;
case 'StoreTrue':
$this->default = false;
break;
case 'StoreFalse':
$this->default = true;
break;
default:
return;
}
}
// }}}
}
@@ -0,0 +1,63 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Outputters common interface, all outputters must implement this interface.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
interface Console_CommandLine_Outputter
{
// stdout() {{{
/**
* Processes the output for a message that should be displayed on STDOUT.
*
* @param string $msg The message to output
*
* @return void
*/
public function stdout($msg);
// }}}
// stderr() {{{
/**
* Processes the output for a message that should be displayed on STDERR.
*
* @param string $msg The message to output
*
* @return void
*/
public function stderr($msg);
// }}}
}
@@ -0,0 +1,82 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* The Outputter interface.
*/
require_once 'Console/CommandLine/Outputter.php';
/**
* Console_CommandLine default Outputter.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Outputter_Default implements Console_CommandLine_Outputter
{
// stdout() {{{
/**
* Writes the message $msg to STDOUT.
*
* @param string $msg The message to output
*
* @return void
*/
public function stdout($msg)
{
if (defined('STDOUT')) {
fwrite(STDOUT, $msg);
} else {
echo $msg;
}
}
// }}}
// stderr() {{{
/**
* Writes the message $msg to STDERR.
*
* @param string $msg The message to output
*
* @return void
*/
public function stderr($msg)
{
if (defined('STDERR')) {
fwrite(STDERR, $msg);
} else {
echo $msg;
}
}
// }}}
}
@@ -0,0 +1,71 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Renderers common interface, all renderers must implement this interface.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
interface Console_CommandLine_Renderer
{
// usage() {{{
/**
* Returns the full usage message.
*
* @return string The usage message
*/
public function usage();
// }}}
// error() {{{
/**
* Returns a formatted error message.
*
* @param string $error The error message to format
*
* @return string The error string
*/
public function error($error);
// }}}
// version() {{{
/**
* Returns the program version string.
*
* @return string The version string
*/
public function version();
// }}}
}
@@ -0,0 +1,430 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* The renderer interface.
*/
require_once 'Console/CommandLine/Renderer.php';
/**
* Console_CommandLine default renderer.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Renderer_Default implements Console_CommandLine_Renderer
{
// Properties {{{
/**
* Integer that define the max width of the help text.
*
* @var integer $line_width Line width
*/
public $line_width = 75;
/**
* Integer that define the max width of the help text.
*
* @var integer $line_width Line width
*/
public $options_on_different_lines = false;
/**
* An instance of Console_CommandLine.
*
* @var Console_CommandLine $parser The parser
*/
public $parser = false;
// }}}
// __construct() {{{
/**
* Constructor.
*
* @param object $parser A Console_CommandLine instance
*
* @return void
*/
public function __construct($parser = false)
{
$this->parser = $parser;
}
// }}}
// usage() {{{
/**
* Returns the full usage message.
*
* @return string The usage message
*/
public function usage()
{
$ret = '';
if (!empty($this->parser->description)) {
$ret .= $this->description() . "\n\n";
}
$ret .= $this->usageLine() . "\n";
if (count($this->parser->commands) > 0) {
$ret .= $this->commandUsageLine() . "\n";
}
if (count($this->parser->options) > 0) {
$ret .= "\n" . $this->optionList() . "\n";
}
if (count($this->parser->args) > 0) {
$ret .= "\n" . $this->argumentList() . "\n";
}
if (count($this->parser->commands) > 0) {
$ret .= "\n" . $this->commandList() . "\n";
}
$ret .= "\n";
return $ret;
}
// }}}
// error() {{{
/**
* Returns a formatted error message.
*
* @param string $error The error message to format
*
* @return string The error string
*/
public function error($error)
{
$ret = 'Error: ' . $error . "\n";
if ($this->parser->add_help_option) {
$name = $this->name();
$ret .= $this->wrap($this->parser->message_provider->get('PROG_HELP_LINE',
array('progname' => $name))) . "\n";
if (count($this->parser->commands) > 0) {
$ret .= $this->wrap($this->parser->message_provider->get('COMMAND_HELP_LINE',
array('progname' => $name))) . "\n";
}
}
return $ret;
}
// }}}
// version() {{{
/**
* Returns the program version string.
*
* @return string The version string
*/
public function version()
{
return $this->parser->message_provider->get('PROG_VERSION_LINE', array(
'progname' => $this->name(),
'version' => $this->parser->version
)) . "\n";
}
// }}}
// name() {{{
/**
* Returns the full name of the program or the sub command
*
* @return string The name of the program
*/
protected function name()
{
$name = $this->parser->name;
$parent = $this->parser->parent;
while ($parent) {
if (count($parent->options) > 0) {
$name = '['
. strtolower($this->parser->message_provider->get('OPTION_WORD',
array('plural' => 's')))
. '] ' . $name;
}
$name = $parent->name . ' ' . $name;
$parent = $parent->parent;
}
return $this->wrap($name);
}
// }}}
// description() {{{
/**
* Returns the command line description message.
*
* @return string The description message
*/
protected function description()
{
return $this->wrap($this->parser->description);
}
// }}}
// usageLine() {{{
/**
* Returns the command line usage message
*
* @return string the usage message
*/
protected function usageLine()
{
$usage = $this->parser->message_provider->get('USAGE_WORD') . ":\n";
$ret = $usage . ' ' . $this->name();
if (count($this->parser->options) > 0) {
$ret .= ' ['
. strtolower($this->parser->message_provider->get('OPTION_WORD'))
. ']';
}
if (count($this->parser->args) > 0) {
foreach ($this->parser->args as $name=>$arg) {
$arg_str = $arg->help_name;
if ($arg->multiple) {
$arg_str .= '1 ' . $arg->help_name . '2 ...';
}
if ($arg->optional) {
$arg_str = '[' . $arg_str . ']';
}
$ret .= ' ' . $arg_str;
}
}
return $this->columnWrap($ret, 2);
}
// }}}
// commandUsageLine() {{{
/**
* Returns the command line usage message for subcommands.
*
* @return string The usage line
*/
protected function commandUsageLine()
{
if (count($this->parser->commands) == 0) {
return '';
}
$ret = ' ' . $this->name();
if (count($this->parser->options) > 0) {
$ret .= ' ['
. strtolower($this->parser->message_provider->get('OPTION_WORD'))
. ']';
}
$ret .= " <command>";
$hasArgs = false;
$hasOptions = false;
foreach ($this->parser->commands as $command) {
if (!$hasArgs && count($command->args) > 0) {
$hasArgs = true;
}
if (!$hasOptions && ($command->add_help_option ||
$command->add_version_option || count($command->options) > 0)) {
$hasOptions = true;
}
}
if ($hasOptions) {
$ret .= ' [options]';
}
if ($hasArgs) {
$ret .= ' [args]';
}
return $this->columnWrap($ret, 2);
}
// }}}
// argumentList() {{{
/**
* Render the arguments list that will be displayed to the user, you can
* override this method if you want to change the look of the list.
*
* @return string The formatted argument list
*/
protected function argumentList()
{
$col = 0;
$args = array();
foreach ($this->parser->args as $arg) {
$argstr = ' ' . $arg->toString();
$args[] = array($argstr, $arg->description);
$ln = strlen($argstr);
if ($col < $ln) {
$col = $ln;
}
}
$ret = $this->parser->message_provider->get('ARGUMENT_WORD') . ":";
foreach ($args as $arg) {
$text = str_pad($arg[0], $col) . ' ' . $arg[1];
$ret .= "\n" . $this->columnWrap($text, $col+2);
}
return $ret;
}
// }}}
// optionList() {{{
/**
* Render the options list that will be displayed to the user, you can
* override this method if you want to change the look of the list.
*
* @return string The formatted option list
*/
protected function optionList()
{
$col = 0;
$options = array();
foreach ($this->parser->options as $option) {
$delim = $this->options_on_different_lines ? "\n" : ', ';
$optstr = $option->toString($delim);
$lines = explode("\n", $optstr);
$lines[0] = ' ' . $lines[0];
if (count($lines) > 1) {
$lines[1] = ' ' . $lines[1];
$ln = strlen($lines[1]);
} else {
$ln = strlen($lines[0]);
}
$options[] = array($lines, $option->description);
if ($col < $ln) {
$col = $ln;
}
}
$ret = $this->parser->message_provider->get('OPTION_WORD') . ":";
foreach ($options as $option) {
if (count($option[0]) > 1) {
$text = str_pad($option[0][1], $col) . ' ' . $option[1];
$pre = $option[0][0] . "\n";
} else {
$text = str_pad($option[0][0], $col) . ' ' . $option[1];
$pre = '';
}
$ret .= "\n" . $pre . $this->columnWrap($text, $col+2);
}
return $ret;
}
// }}}
// commandList() {{{
/**
* Render the command list that will be displayed to the user, you can
* override this method if you want to change the look of the list.
*
* @return string The formatted subcommand list
*/
protected function commandList()
{
$commands = array();
$col = 0;
foreach ($this->parser->commands as $cmdname=>$command) {
$cmdname = ' ' . $cmdname;
$commands[] = array($cmdname, $command->description, $command->aliases);
$ln = strlen($cmdname);
if ($col < $ln) {
$col = $ln;
}
}
$ret = $this->parser->message_provider->get('COMMAND_WORD') . ":";
foreach ($commands as $command) {
$text = str_pad($command[0], $col) . ' ' . $command[1];
if ($aliasesCount = count($command[2])) {
$pad = '';
$text .= ' (';
$text .= $aliasesCount > 1 ? 'aliases: ' : 'alias: ';
foreach ($command[2] as $alias) {
$text .= $pad . $alias;
$pad = ', ';
}
$text .= ')';
}
$ret .= "\n" . $this->columnWrap($text, $col+2);
}
return $ret;
}
// }}}
// wrap() {{{
/**
* Wraps the text passed to the method.
*
* @param string $text The text to wrap
* @param int $lw The column width (defaults to line_width property)
*
* @return string The wrapped text
*/
protected function wrap($text, $lw=null)
{
if ($this->line_width > 0) {
if ($lw === null) {
$lw = $this->line_width;
}
return wordwrap($text, $lw, "\n", false);
}
return $text;
}
// }}}
// columnWrap() {{{
/**
* Wraps the text passed to the method at the specified width.
*
* @param string $text The text to wrap
* @param int $cw The wrap width
*
* @return string The wrapped text
*/
protected function columnWrap($text, $cw)
{
$tokens = explode("\n", $this->wrap($text));
$ret = $tokens[0];
$text = trim(substr($text, strlen($ret)));
if (empty($text)) {
return $ret;
}
$chunks = $this->wrap($text, $this->line_width - $cw);
$tokens = explode("\n", $chunks);
foreach ($tokens as $token) {
if (!empty($token)) {
$ret .= "\n" . str_repeat(' ', $cw) . $token;
} else {
$ret .= "\n";
}
}
return $ret;
}
// }}}
}
@@ -0,0 +1,71 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* A lightweight class to store the result of the command line parsing.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_Result
{
// Public properties {{{
/**
* The result options associative array.
* Key is the name of the option and value its value.
*
* @var array $options Result options array
*/
public $options = array();
/**
* The result arguments array.
*
* @var array $args Result arguments array
*/
public $args = array();
/**
* Name of the command invoked by the user, false if no command invoked.
*
* @var string $command_name Result command name
*/
public $command_name = false;
/**
* A result instance for the subcommand.
*
* @var Console_CommandLine_Result Result instance for the subcommand
*/
public $command = false;
// }}}
}
@@ -0,0 +1,318 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* This file is part of the PEAR Console_CommandLine package.
*
* PHP version 5
*
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_CommandLine
* @since File available since release 0.1.0
* @filesource
*/
/**
* Required file
*/
require_once 'Console/CommandLine.php';
/**
* Parser for command line xml definitions.
*
* @category Console
* @package Console_CommandLine
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2007 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: @package_version@
* @link http://pear.php.net/package/Console_CommandLine
* @since Class available since release 0.1.0
*/
class Console_CommandLine_XmlParser
{
// parse() {{{
/**
* Parses the given xml definition file and returns a
* Console_CommandLine instance constructed with the xml data.
*
* @param string $xmlfile The xml file to parse
*
* @return Console_CommandLine A parser instance
*/
public static function parse($xmlfile)
{
if (!is_readable($xmlfile)) {
Console_CommandLine::triggerError('invalid_xml_file',
E_USER_ERROR, array('{$file}' => $xmlfile));
}
$doc = new DomDocument();
$doc->load($xmlfile);
self::validate($doc);
$nodes = $doc->getElementsByTagName('command');
$root = $nodes->item(0);
return self::_parseCommandNode($root, true);
}
// }}}
// parseString() {{{
/**
* Parses the given xml definition string and returns a
* Console_CommandLine instance constructed with the xml data.
*
* @param string $xmlstr The xml string to parse
*
* @return Console_CommandLine A parser instance
*/
public static function parseString($xmlstr)
{
$doc = new DomDocument();
$doc->loadXml($xmlstr);
self::validate($doc);
$nodes = $doc->getElementsByTagName('command');
$root = $nodes->item(0);
return self::_parseCommandNode($root, true);
}
// }}}
// validate() {{{
/**
* Validates the xml definition using Relax NG.
*
* @param DomDocument $doc The document to validate
*
* @return boolean Whether the xml data is valid or not.
* @throws Console_CommandLine_Exception
* @todo use exceptions
*/
public static function validate($doc)
{
$pkgRoot = __DIR__ . '/../../';
$paths = array(
// PEAR/Composer
'@data_dir@/Console_CommandLine/data/xmlschema.rng',
// Composer
$pkgRoot . 'data/Console_CommandLine/data/xmlschema.rng',
$pkgRoot . 'data/console_commandline/data/xmlschema.rng',
// Git
$pkgRoot . 'data/xmlschema.rng',
'xmlschema.rng',
);
foreach ($paths as $path) {
if (is_readable($path)) {
return $doc->relaxNGValidate($path);
}
}
Console_CommandLine::triggerError(
'invalid_xml_file',
E_USER_ERROR, array('{$file}' => $rngfile));
}
// }}}
// _parseCommandNode() {{{
/**
* Parses the root command node or a command node and returns the
* constructed Console_CommandLine or Console_CommandLine_Command instance.
*
* @param DomDocumentNode $node The node to parse
* @param bool $isRootNode Whether it is a root node or not
*
* @return mixed Console_CommandLine or Console_CommandLine_Command
*/
private static function _parseCommandNode($node, $isRootNode = false)
{
if ($isRootNode) {
$obj = new Console_CommandLine();
} else {
include_once 'Console/CommandLine/Command.php';
$obj = new Console_CommandLine_Command();
}
foreach ($node->childNodes as $cNode) {
$cNodeName = $cNode->nodeName;
switch ($cNodeName) {
case 'name':
case 'description':
case 'version':
$obj->$cNodeName = trim($cNode->nodeValue);
break;
case 'add_help_option':
case 'add_version_option':
case 'force_posix':
$obj->$cNodeName = self::_bool(trim($cNode->nodeValue));
break;
case 'option':
$obj->addOption(self::_parseOptionNode($cNode));
break;
case 'argument':
$obj->addArgument(self::_parseArgumentNode($cNode));
break;
case 'command':
$obj->addCommand(self::_parseCommandNode($cNode));
break;
case 'aliases':
if (!$isRootNode) {
foreach ($cNode->childNodes as $subChildNode) {
if ($subChildNode->nodeName == 'alias') {
$obj->aliases[] = trim($subChildNode->nodeValue);
}
}
}
break;
case 'messages':
$obj->messages = self::_messages($cNode);
break;
default:
break;
}
}
return $obj;
}
// }}}
// _parseOptionNode() {{{
/**
* Parses an option node and returns the constructed
* Console_CommandLine_Option instance.
*
* @param DomDocumentNode $node The node to parse
*
* @return Console_CommandLine_Option The built option
*/
private static function _parseOptionNode($node)
{
include_once 'Console/CommandLine/Option.php';
$obj = new Console_CommandLine_Option($node->getAttribute('name'));
foreach ($node->childNodes as $cNode) {
$cNodeName = $cNode->nodeName;
switch ($cNodeName) {
case 'choices':
foreach ($cNode->childNodes as $subChildNode) {
if ($subChildNode->nodeName == 'choice') {
$obj->choices[] = trim($subChildNode->nodeValue);
}
}
break;
case 'messages':
$obj->messages = self::_messages($cNode);
break;
default:
if (property_exists($obj, $cNodeName)) {
$obj->$cNodeName = trim($cNode->nodeValue);
}
break;
}
}
if ($obj->action == 'Password') {
$obj->argument_optional = true;
}
return $obj;
}
// }}}
// _parseArgumentNode() {{{
/**
* Parses an argument node and returns the constructed
* Console_CommandLine_Argument instance.
*
* @param DomDocumentNode $node The node to parse
*
* @return Console_CommandLine_Argument The built argument
*/
private static function _parseArgumentNode($node)
{
include_once 'Console/CommandLine/Argument.php';
$obj = new Console_CommandLine_Argument($node->getAttribute('name'));
foreach ($node->childNodes as $cNode) {
$cNodeName = $cNode->nodeName;
switch ($cNodeName) {
case 'description':
case 'help_name':
case 'default':
$obj->$cNodeName = trim($cNode->nodeValue);
break;
case 'multiple':
$obj->multiple = self::_bool(trim($cNode->nodeValue));
break;
case 'optional':
$obj->optional = self::_bool(trim($cNode->nodeValue));
break;
case 'choices':
foreach ($cNode->childNodes as $subChildNode) {
if ($subChildNode->nodeName == 'choice') {
$obj->choices[] = trim($subChildNode->nodeValue);
}
}
break;
case 'messages':
$obj->messages = self::_messages($cNode);
break;
default:
break;
}
}
return $obj;
}
// }}}
// _bool() {{{
/**
* Returns a boolean according to true/false possible strings.
*
* @param string $str The string to process
*
* @return boolean
*/
private static function _bool($str)
{
return in_array(strtolower((string)$str), array('true', '1', 'on', 'yes'));
}
// }}}
// _messages() {{{
/**
* Returns an array of custom messages for the element
*
* @param DOMNode $node The messages node to process
*
* @return array an array of messages
*
* @see Console_CommandLine::$messages
* @see Console_CommandLine_Element::$messages
*/
private static function _messages(DOMNode $node)
{
$messages = array();
foreach ($node->childNodes as $cNode) {
if ($cNode->nodeType == XML_ELEMENT_NODE) {
$name = $cNode->getAttribute('name');
$value = trim($cNode->nodeValue);
$messages[$name] = $value;
}
}
return $messages;
}
// }}}
}
+55
View File
@@ -0,0 +1,55 @@
*******************
Console_CommandLine
*******************
A full featured command line options and arguments parser.
``Console_CommandLine`` is a full featured package for managing command-line
options and arguments highly inspired from python ``optparse`` module, it allows
the developer to easily build complex command line interfaces.
=============
Main features
=============
* handles sub commands (ie. ``$ myscript.php -q subcommand -f file``),
* can be completely built from an XML definition file,
* generate ``--help`` and ``--version`` options automatically,
* can be completely customized,
* builtin support for i18n,
* and much more...
============
Installation
============
PEAR
====
::
$ pear install Console_CommandLine
Composer
========
::
$ composer require pear/console_commandline
=====
Links
=====
Homepage
http://pear.php.net/package/Console_CommandLine
Bug tracker
http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine
Documentation
http://pear.php.net/manual/en/package.console.console-commandline.php
Unit test status
https://travis-ci.org/pear/Console_CommandLine
.. image:: https://travis-ci.org/pear/Console_CommandLine.svg?branch=stable
:target: https://travis-ci.org/pear/Console_CommandLine
Packagist
https://packagist.org/packages/pear/console_commandline
@@ -0,0 +1,42 @@
{
"name": "pear/console_commandline",
"description": "A full featured command line options and arguments parser.",
"type": "library",
"keywords": [
"console"
],
"homepage": "https://github.com/pear/Console_CommandLine",
"license": "MIT",
"authors": [
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com"
},
{
"name": "David Jean Louis",
"email": "izimobil@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"pear/pear_exception": "^1.0.0",
"ext-dom": "*",
"ext-xml": "*"
},
"autoload": {
"psr-0": {
"Console": "./"
},
"exclude-from-classmap": ["tests/"]
},
"include-path": [
""
],
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine",
"source": "https://github.com/pear/Console_CommandLine"
},
"require-dev": {
"phpunit/phpunit": "*"
}
}
@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the RNG file for validating Console_CommandLine xml definitions.
Author : David JEAN LOUIS
Licence : MIT License
Version : CVS: $Id$
-->
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<!-- structure -->
<start>
<ref name="ref_command"/>
</start>
<!-- Command node -->
<define name="ref_command_subcommand_common">
<interleave>
<optional>
<element name="name">
<text/>
</element>
</optional>
<optional>
<element name="description">
<text/>
</element>
</optional>
<optional>
<element name="version">
<text/>
</element>
</optional>
<optional>
<element name="add_help_option">
<ref name="ref_bool_choices"/>
</element>
</optional>
<optional>
<element name="add_version_option">
<ref name="ref_bool_choices"/>
</element>
</optional>
<optional>
<element name="force_posix">
<ref name="ref_bool_choices"/>
</element>
</optional>
<optional>
<ref name="ref_messages_common"/>
</optional>
<zeroOrMore>
<ref name="ref_option"/>
</zeroOrMore>
<zeroOrMore>
<ref name="ref_argument"/>
</zeroOrMore>
<zeroOrMore>
<ref name="ref_subcommand"/>
</zeroOrMore>
</interleave>
</define>
<!-- command element -->
<define name="ref_command">
<element name="command">
<interleave>
<ref name="ref_command_subcommand_common"/>
</interleave>
</element>
</define>
<!-- subcommand element -->
<define name="ref_subcommand">
<element name="command">
<interleave>
<ref name="ref_command_subcommand_common"/>
<optional>
<element name="aliases">
<zeroOrMore>
<element name="alias">
<text/>
</element>
</zeroOrMore>
</element>
</optional>
</interleave>
</element>
</define>
<!-- custom messages common element -->
<define name="ref_messages_common">
<element name="messages">
<oneOrMore>
<element name="message">
<attribute name="name">
<data type="string"/>
</attribute>
<text/>
</element>
</oneOrMore>
</element>
</define>
<!-- options and arguments common elements -->
<define name="ref_option_argument_common">
<interleave>
<optional>
<element name="description">
<text/>
</element>
</optional>
<optional>
<element name="help_name">
<text/>
</element>
</optional>
<optional>
<element name="default">
<text/>
</element>
</optional>
<optional>
<ref name="ref_messages_common"/>
</optional>
</interleave>
</define>
<!-- Option node -->
<define name="ref_option">
<element name="option">
<attribute name="name">
<data type="string"/>
</attribute>
<interleave>
<optional>
<element name="short_name">
<text/>
</element>
</optional>
<optional>
<element name="long_name">
<text/>
</element>
</optional>
<ref name="ref_option_argument_common"/>
<optional>
<element name="action">
<text/>
</element>
</optional>
<optional>
<element name="choices">
<zeroOrMore>
<element name="choice">
<text/>
</element>
</zeroOrMore>
</element>
</optional>
<optional>
<element name="add_list_option">
<ref name="ref_bool_choices"/>
</element>
</optional>
</interleave>
</element>
</define>
<!-- Argument node -->
<define name="ref_argument">
<element name="argument">
<attribute name="name">
<data type="string"/>
</attribute>
<interleave>
<ref name="ref_option_argument_common"/>
<optional>
<element name="multiple">
<ref name="ref_bool_choices"/>
</element>
</optional>
<optional>
<element name="optional">
<ref name="ref_bool_choices"/>
</element>
</optional>
<optional>
<element name="choices">
<zeroOrMore>
<element name="choice">
<text/>
</element>
</zeroOrMore>
</element>
</optional>
</interleave>
</element>
</define>
<!-- boolean choices -->
<define name="ref_bool_choices">
<choice>
<data type="token">
<param name="pattern">[Tt][Rr][Uu][Ee]</param>
</data>
<data type="token">
<param name="pattern">[On][Nn]</param>
</data>
<data type="token">
<param name="pattern">[Yy][Ee][Ss]</param>
</data>
<value>1</value>
<data type="token">
<param name="pattern">[Ff][Aa][Ll][Ss][Ee]</param>
</data>
<data type="token">
<param name="pattern">[Of][Ff][Ff]</param>
</data>
<data type="token">
<param name="pattern">[Nn][Oo]</param>
</data>
<value>0</value>
</choice>
</define>
</grammar>
+10
View File
@@ -0,0 +1,10 @@
<phpunit>
<testsuites>
<testsuite name="tests">
<directory suffix=".phpt">tests/</directory>
</testsuite>
</testsuites>
<php>
<env name="XDEBUG_MODE" value="off"/>
</php>
</phpunit>
+365
View File
@@ -0,0 +1,365 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* PHP Version 5
*
* Copyright (c) 2001-2015, The PEAR developers
*
* This source file is subject to the BSD-2-Clause license,
* that is bundled with this package in the file LICENSE, and is
* available through the world-wide-web at the following url:
* http://opensource.org/licenses/bsd-license.php.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_Getopt
*/
require_once 'PEAR.php';
/**
* Command-line options parsing class.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
* @link http://pear.php.net/package/Console_Getopt
*/
class Console_Getopt
{
/**
* Parses the command-line options.
*
* The first parameter to this function should be the list of command-line
* arguments without the leading reference to the running program.
*
* The second parameter is a string of allowed short options. Each of the
* option letters can be followed by a colon ':' to specify that the option
* requires an argument, or a double colon '::' to specify that the option
* takes an optional argument.
*
* The third argument is an optional array of allowed long options. The
* leading '--' should not be included in the option name. Options that
* require an argument should be followed by '=', and options that take an
* option argument should be followed by '=='.
*
* The return value is an array of two elements: the list of parsed
* options and the list of non-option command-line arguments. Each entry in
* the list of parsed options is a pair of elements - the first one
* specifies the option, and the second one specifies the option argument,
* if there was one.
*
* Long and short options can be mixed.
*
* Most of the semantics of this function are based on GNU getopt_long().
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
}
/**
* This function expects $args to start with the script name (POSIX-style).
* Preserved for backwards compatibility.
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
*
* @see getopt2()
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
}
/**
* The actual implementation of the argument parsing code.
*
* @param int $version Version to use
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array
*/
public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
{
// in case you pass directly readPHPArgv() as the first arg
if (PEAR::isError($args)) {
return $args;
}
if (empty($args)) {
return array(array(), array());
}
$non_opts = $opts = array();
settype($args, 'array');
if ($long_options) {
sort($long_options);
}
/*
* Preserve backwards compatibility with callers that relied on
* erroneous POSIX fix.
*/
if ($version < 2) {
if (isset($args[0][0]) && $args[0][0] != '-') {
array_shift($args);
}
}
for ($i = 0; $i < count($args); $i++) {
$arg = $args[$i];
/* The special element '--' means explicit end of
options. Treat the rest of the arguments as non-options
and end the loop. */
if ($arg == '--') {
$non_opts = array_merge($non_opts, array_slice($args, $i + 1));
break;
}
if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) {
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} elseif (strlen($arg) > 1 && $arg[1] == '-') {
$error = Console_Getopt::_parseLongOption(substr($arg, 2),
$long_options,
$opts,
$i,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
} elseif ($arg == '-') {
// - is stdin
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} else {
$error = Console_Getopt::_parseShortOption(substr($arg, 1),
$short_options,
$opts,
$i,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
}
}
return array($opts, $non_opts);
}
/**
* Parse short option
*
* @param string $arg Argument
* @param string[] $short_options Available short options
* @param string[][] &$opts
* @param int &$argIdx
* @param string[] $args
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return void
*/
protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown)
{
for ($i = 0; $i < strlen($arg); $i++) {
$opt = $arg[$i];
$opt_arg = null;
/* Try to find the short option in the specifier string. */
if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') {
if ($skip_unknown === true) {
break;
}
$msg = "Console_Getopt: unrecognized option -- $opt";
return PEAR::raiseError($msg);
}
if (strlen($spec) > 1 && $spec[1] == ':') {
if (strlen($spec) > 2 && $spec[2] == ':') {
if ($i + 1 < strlen($arg)) {
/* Option takes an optional argument. Use the remainder of
the arg string if there is anything left. */
$opts[] = array($opt, substr($arg, $i + 1));
break;
}
} else {
/* Option requires an argument. Use the remainder of the arg
string if there is anything left. */
if ($i + 1 < strlen($arg)) {
$opts[] = array($opt, substr($arg, $i + 1));
break;
} else if (isset($args[++$argIdx])) {
$opt_arg = $args[$argIdx];
/* Else use the next argument. */;
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
} else {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
}
}
$opts[] = array($opt, $opt_arg);
}
}
/**
* Checks if an argument is a short option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isShortOpt($arg)
{
return strlen($arg) == 2 && $arg[0] == '-'
&& preg_match('/[a-zA-Z]/', $arg[1]);
}
/**
* Checks if an argument is a long option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isLongOpt($arg)
{
return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
preg_match('/[a-zA-Z]+$/', substr($arg, 2));
}
/**
* Parse long option
*
* @param string $arg Argument
* @param string[] $long_options Available long options
* @param string[][] &$opts
* @param int &$argIdx
* @param string[] $args
*
* @return void|PEAR_Error
*/
protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown)
{
@list($opt, $opt_arg) = explode('=', $arg, 2);
$opt_len = strlen($opt);
for ($i = 0; $i < count($long_options); $i++) {
$long_opt = $long_options[$i];
$opt_start = substr($long_opt, 0, $opt_len);
$long_opt_name = str_replace('=', '', $long_opt);
/* Option doesn't match. Go on to the next one. */
if ($long_opt_name != $opt) {
continue;
}
$opt_rest = substr($long_opt, $opt_len);
/* Check that the options uniquely matches one of the allowed
options. */
if ($i + 1 < count($long_options)) {
$next_option_rest = substr($long_options[$i + 1], $opt_len);
} else {
$next_option_rest = '';
}
if ($opt_rest != '' && $opt[0] != '=' &&
$i + 1 < count($long_options) &&
$opt == substr($long_options[$i+1], 0, $opt_len) &&
$next_option_rest != '' &&
$next_option_rest[0] != '=') {
$msg = "Console_Getopt: option --$opt is ambiguous";
return PEAR::raiseError($msg);
}
if (substr($long_opt, -1) == '=') {
if (substr($long_opt, -2) != '==') {
/* Long option requires an argument.
Take the next argument if one wasn't specified. */;
if (!strlen($opt_arg)) {
if (!isset($args[++$argIdx])) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
$opt_arg = $args[$argIdx];
}
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
}
} else if ($opt_arg) {
$msg = "Console_Getopt: option --$opt doesn't allow an argument";
return PEAR::raiseError($msg);
}
$opts[] = array('--' . $opt, $opt_arg);
return;
}
if ($skip_unknown === true) {
return;
}
return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
}
/**
* Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives
*
* @return mixed the $argv PHP array or PEAR error if not registered
*/
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
}
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2001-2015, The PEAR developers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
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
HOLDER 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.
+26
View File
@@ -0,0 +1,26 @@
*******************************************
Console_Getopt - Command-line option parser
*******************************************
This is a PHP implementation of "getopt" supporting both short and long options.
It helps parsing command line options in your PHP script.
Homepage: http://pear.php.net/package/Console_Getopt
.. image:: https://travis-ci.org/pear/Console_Getopt.svg?branch=master
:target: https://travis-ci.org/pear/Console_Getopt
Alternatives
============
* Console_CommandLine__ (recommended)
* Console_GetoptPlus__
__ http://pear.php.net/package/Console_CommandLine
__ http://pear.php.net/package/Console_GetoptPlus
License
=======
BSD-2-Clause
+35
View File
@@ -0,0 +1,35 @@
{
"authors": [
{
"email": "andrei@php.net",
"name": "Andrei Zmievski",
"role": "Lead"
},
{
"email": "stig@php.net",
"name": "Stig Bakken",
"role": "Developer"
},
{
"email": "cellog@php.net",
"name": "Greg Beaver",
"role": "Helper"
}
],
"autoload": {
"psr-0": {
"Console": "./"
}
},
"description": "More info available on: http://pear.php.net/package/Console_Getopt",
"include-path": [
"./"
],
"license": "BSD-2-Clause",
"name": "pear/console_getopt",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt",
"source": "https://github.com/pear/Console_Getopt"
},
"type": "library"
}
+302
View File
@@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.2" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>Console_Getopt</name>
<channel>pear.php.net</channel>
<summary>Command-line option parser</summary>
<description>This is a PHP implementation of &quot;getopt&quot; supporting both
short and long options.</description>
<lead>
<name>Andrei Zmievski</name>
<user>andrei</user>
<email>andrei@php.net</email>
<active>no</active>
</lead>
<developer>
<name>Stig Bakken</name>
<user>ssb</user>
<email>stig@php.net</email>
<active>no</active>
</developer>
<helper>
<name>Greg Beaver</name>
<user>cellog</user>
<email>cellog@php.net</email>
<active>no</active>
</helper>
<date>2019-11-20</date>
<version>
<release>1.4.3</release>
<api>1.4.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
<notes>
* PR #4: Fix PHP 7.4 deprecation: array/string curly braces access
* PR #5: fix phplint warnings
</notes>
<contents>
<dir name="/">
<dir name="Console">
<file name="Getopt.php" role="php" />
</dir>
<dir name="tests">
<file role="test" name="001-getopt.phpt" />
<file role="test" name="bug10557.phpt" />
<file role="test" name="bug11068.phpt" />
<file role="test" name="bug13140.phpt" />
</dir>
</dir>
</contents>
<compatible>
<name>PEAR</name>
<channel>pear.php.net</channel>
<min>1.4.0</min>
<max>1.999.999</max>
</compatible>
<dependencies>
<required>
<php>
<min>5.4.0</min>
</php>
<pearinstaller>
<min>1.8.0</min>
</pearinstaller>
</required>
</dependencies>
<phprelease />
<changelog>
<release>
<date>2019-11-20</date>
<version>
<release>1.4.3</release>
<api>1.4.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
<notes>
* PR #4: Fix PHP 7.4 deprecation: array/string curly braces access
* PR #5: fix phplint warnings
</notes>
</release>
<release>
<date>2019-02-06</date>
<version>
<release>1.4.2</release>
<api>1.4.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
<notes>
* Remove use of each(), which is removed in PHP 8
</notes>
</release>
<release>
<date>2015-07-20</date>
<version>
<release>1.4.1</release>
<api>1.4.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
<notes>
* Fix unit test on PHP 7 [cweiske]
</notes>
</release>
<release>
<date>2015-02-22</date>
<version>
<release>1.4.0</release>
<api>1.4.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
<notes>
* Change license to BSD-2-Clause
* Set minimum PHP version to 5.4.0
* Mark static methods with "static" keyword
</notes>
</release>
<release>
<date>2011-03-07</date>
<version>
<release>1.3.1</release>
<api>1.3.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
* Change the minimum PEAR installer dep to be lower
</notes>
</release>
<release>
<date>2010-12-11</date>
<time>20:20:13</time>
<version>
<release>1.3.0</release>
<api>1.3.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
* Implement Request #13140: [PATCH] to skip unknown parameters. [patch by rquadling, improved on by dufuz]
</notes>
</release>
<release>
<date>2007-06-12</date>
<version>
<release>1.2.3</release>
<api>1.2.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
* fix Bug #11068: No way to read plain &quot;-&quot; option [cardoe]
</notes>
</release>
<release>
<version>
<release>1.2.2</release>
<api>1.2.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2007-02-17</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
* fix Bug #4475: An ambiguous error occurred when specifying similar longoption name.
* fix Bug #10055: Not failing properly on short options missing required values
</notes>
</release>
<release>
<version>
<release>1.2.1</release>
<api>1.2.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2006-12-08</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
Fixed bugs #4448 (Long parameter values truncated with longoption parameter) and #7444 (Trailing spaces after php closing tag)
</notes>
</release>
<release>
<version>
<release>1.2</release>
<api>1.2</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2003-12-11</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
Fix to preserve BC with 1.0 and allow correct behaviour for new users
</notes>
</release>
<release>
<version>
<release>1.0</release>
<api>1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2002-09-13</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
Stable release
</notes>
</release>
<release>
<version>
<release>0.11</release>
<api>0.11</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2002-05-26</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
POSIX getopt compatibility fix: treat first element of args
array as command name
</notes>
</release>
<release>
<version>
<release>0.10</release>
<api>0.10</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2002-05-12</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
Packaging fix
</notes>
</release>
<release>
<version>
<release>0.9</release>
<api>0.9</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2002-05-12</date>
<license uri="http://www.php.net/license">PHP License</license>
<notes>
Initial release
</notes>
</release>
</changelog>
</package>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,483 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Various exception handling classes for Crypt_GPG
*
* Crypt_GPG provides an object oriented interface to GNU Privacy
* Guard (GPG). It requires the GPG executable to be on the system.
*
* This file contains various exception classes used by the Crypt_GPG package.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2011 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* PEAR Exception handler and base class
*/
require_once 'PEAR/Exception.php';
/**
* An exception thrown by the Crypt_GPG package
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_Exception extends PEAR_Exception
{
}
/**
* An exception thrown when a file is used in ways it cannot be used
*
* For example, if an output file is specified and the file is not writeable, or
* if an input file is specified and the file is not readable, this exception
* is thrown.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2007-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_FileException extends Crypt_GPG_Exception
{
/**
* The name of the file that caused this exception
*
* @var string
*/
private $_filename = '';
/**
* Creates a new Crypt_GPG_FileException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $filename the name of the file that caused this exception.
*/
public function __construct($message, $code = 0, $filename = '')
{
$this->_filename = $filename;
parent::__construct($message, $code);
}
/**
* Returns the filename of the file that caused this exception
*
* @return string the filename of the file that caused this exception.
*
* @see Crypt_GPG_FileException::$_filename
*/
public function getFilename()
{
return $this->_filename;
}
}
/**
* An exception thrown when the GPG subprocess cannot be opened
*
* This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a
* new subprocess and fails.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception
{
/**
* The command used to try to open the subprocess
*
* @var string
*/
private $_command = '';
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $command the command that was called to open the
* new subprocess.
*
* @see Crypt_GPG::_openSubprocess()
*/
public function __construct($message, $code = 0, $command = '')
{
$this->_command = $command;
parent::__construct($message, $code);
}
/**
* Returns the contents of the internal _command property
*
* @return string the command used to open the subprocess.
*
* @see Crypt_GPG_OpenSubprocessException::$_command
*/
public function getCommand()
{
return $this->_command;
}
}
/**
* An exception thrown when an invalid GPG operation is attempted
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception
{
/**
* The attempted operation
*
* @var string
*/
private $_operation = '';
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $operation the operation.
*/
public function __construct($message, $code = 0, $operation = '')
{
$this->_operation = $operation;
parent::__construct($message, $code);
}
/**
* Returns the contents of the internal _operation property
*
* @return string the attempted operation.
*
* @see Crypt_GPG_InvalidOperationException::$_operation
*/
public function getOperation()
{
return $this->_operation;
}
}
/**
* An exception thrown when Crypt_GPG fails to find the key for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception
{
/**
* The key identifier that was searched for
*
* @var string
*/
private $_keyId = '';
/**
* Creates a new Crypt_GPG_KeyNotFoundException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the key.
*/
public function __construct($message, $code = 0, $keyId= '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
}
/**
* An exception thrown when Crypt_GPG cannot find valid data for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_NoDataException extends Crypt_GPG_Exception
{
}
/**
* An exception thrown when a required passphrase is incorrect or missing
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception
{
/**
* Keys for which the passhprase is missing
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_missingPassphrases = array();
/**
* Keys for which the passhprase is incorrect
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_badPassphrases = array();
/**
* Creates a new Crypt_GPG_BadPassphraseException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param array $badPassphrases an array containing user ids of keys
* for which the passphrase is incorrect.
* @param array $missingPassphrases an array containing user ids of keys
* for which the passphrase is missing.
*/
public function __construct($message, $code = 0,
array $badPassphrases = array(), array $missingPassphrases = array()
) {
$this->_badPassphrases = (array) $badPassphrases;
$this->_missingPassphrases = (array) $missingPassphrases;
parent::__construct($message, $code);
}
/**
* Gets keys for which the passhprase is incorrect
*
* @return array an array of keys for which the passphrase is incorrect.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getBadPassphrases()
{
return $this->_badPassphrases;
}
/**
* Gets keys for which the passhprase is missing
*
* @return array an array of keys for which the passphrase is missing.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getMissingPassphrases()
{
return $this->_missingPassphrases;
}
}
/**
* An exception thrown when an attempt is made to delete public key that has an
* associated private key on the keyring
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception
{
/**
* The key identifier the deletion attempt was made upon
*
* @var string
*/
private $_keyId = '';
/**
* Creates a new Crypt_GPG_DeletePrivateKeyException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the public key that was
* attempted to delete.
*
* @see Crypt_GPG::deletePublicKey()
*/
public function __construct($message, $code = 0, $keyId = '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
}
/**
* An exception thrown when an attempt is made to generate a key and the
* attempt fails
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2011 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_KeyNotCreatedException extends Crypt_GPG_Exception
{
}
/**
* An exception thrown when an attempt is made to generate a key and the
* key parameters set on the key generator are invalid
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2011 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_InvalidKeyParamsException extends Crypt_GPG_Exception
{
/**
* The key algorithm
*
* @var integer
*/
private $_algorithm = 0;
/**
* The key size
*
* @var integer
*/
private $_size = 0;
/**
* The key usage
*
* @var integer
*/
private $_usage = 0;
/**
* Creates a new Crypt_GPG_InvalidKeyParamsException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $algorithm the key algorithm.
* @param string $size the key size.
* @param string $usage the key usage.
*/
public function __construct(
$message,
$code = 0,
$algorithm = 0,
$size = 0,
$usage = 0
) {
parent::__construct($message, $code);
$this->_algorithm = $algorithm;
$this->_size = $size;
$this->_usage = $usage;
}
/**
* Gets the key algorithm
*
* @return integer the key algorithm.
*/
public function getAlgorithm()
{
return $this->_algorithm;
}
/**
* Gets the key size
*
* @return integer the key size.
*/
public function getSize()
{
return $this->_size;
}
/**
* Gets the key usage
*
* @return integer the key usage.
*/
public function getUsage()
{
return $this->_usage;
}
}
+205
View File
@@ -0,0 +1,205 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG keys
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* Sub-key class definition
*/
require_once 'Crypt/GPG/SubKey.php';
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
/**
* A data class for GPG key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
*/
class Crypt_GPG_Key
{
/**
* The user ids associated with this key
*
* This is an array of {@link Crypt_GPG_UserId} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addUserId()
* @see Crypt_GPG_Key::getUserIds()
*/
private $_userIds = array();
/**
* The subkeys of this key
*
* This is an array of {@link Crypt_GPG_SubKey} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addSubKey()
* @see Crypt_GPG_Key::getSubKeys()
*/
private $_subKeys = array();
/**
* Gets the sub-keys of this key
*
* @return array the sub-keys of this key.
*
* @see Crypt_GPG_Key::addSubKey()
*/
public function getSubKeys()
{
return $this->_subKeys;
}
/**
* Gets the user ids of this key
*
* @return array the user ids of this key.
*
* @see Crypt_GPG_Key::addUserId()
*/
public function getUserIds()
{
return $this->_userIds;
}
/**
* Gets the primary sub-key of this key
*
* The primary key is the first added sub-key.
*
* @return Crypt_GPG_SubKey the primary sub-key of this key.
*/
public function getPrimaryKey()
{
$primary_key = null;
if (count($this->_subKeys) > 0) {
$primary_key = $this->_subKeys[0];
}
return $primary_key;
}
/**
* Gets whether or not this key can sign data
*
* This key can sign data if any sub-key of this key can sign data.
*
* @return boolean true if this key can sign data and false if this key
* cannot sign data.
*/
public function canSign()
{
$canSign = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canSign()) {
$canSign = true;
break;
}
}
return $canSign;
}
/**
* Gets whether or not this key can encrypt data
*
* This key can encrypt data if any sub-key of this key can encrypt data.
*
* @return boolean true if this key can encrypt data and false if this
* key cannot encrypt data.
*/
public function canEncrypt()
{
$canEncrypt = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canEncrypt()) {
$canEncrypt = true;
break;
}
}
return $canEncrypt;
}
/**
* Adds a sub-key to this key
*
* The first added sub-key will be the primary key of this key.
*
* @param Crypt_GPG_SubKey $subKey the sub-key to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addSubKey(Crypt_GPG_SubKey $subKey)
{
$this->_subKeys[] = $subKey;
return $this;
}
/**
* Adds a user id to this key
*
* @param Crypt_GPG_UserId $userId the user id to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addUserId(Crypt_GPG_UserId $userId)
{
$this->_userIds[] = $userId;
return $this;
}
/**
* String representation of the key
*
* @return string The key ID.
*/
public function __toString()
{
foreach ($this->_subKeys as $subKey) {
if ($id = $subKey->getId()) {
return $id;
}
}
return '';
}
}
@@ -0,0 +1,594 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains an object that handles GnuPG key generation.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2011-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* Base class for GPG methods
*/
require_once 'Crypt/GPGAbstract.php';
/**
* GnuPG key generator
*
* This class provides an object oriented interface for generating keys with
* the GNU Privacy Guard (GPG).
*
* Secure key generation requires true random numbers, and as such can be slow.
* If the operating system runs out of entropy, key generation will block until
* more entropy is available.
*
* If quick key generation is important, a hardware entropy generator, or an
* entropy gathering daemon may be installed. For example, administrators of
* Debian systems may want to install the 'randomsound' package.
*
* This class uses the experimental automated key generation support available
* in GnuPG. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on the key generation format.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_KeyGenerator extends Crypt_GPGAbstract
{
/**
* The expiration date of generated keys
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setExpirationDate()
*/
protected $expirationDate = 0;
/**
* The passphrase of generated keys
*
* @var string
*
* @see Crypt_GPG_KeyGenerator::setPassphrase()
*/
protected $passphrase = '';
/**
* The algorithm for generated primary keys
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setKeyParams()
*/
protected $keyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_DSA;
/**
* The size of generated primary keys
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setKeyParams()
*/
protected $keySize = 1024;
/**
* The usages of generated primary keys
*
* This is a bitwise combination of the usage constants in
* {@link Crypt_GPG_SubKey}.
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setKeyParams()
*/
protected $keyUsage = 6; // USAGE_SIGN | USAGE_CERTIFY
/**
* The algorithm for generated sub-keys
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setSubKeyParams()
*/
protected $subKeyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC;
/**
* The size of generated sub-keys
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setSubKeyParams()
*/
protected $subKeySize = 2048;
/**
* The usages of generated sub-keys
*
* This is a bitwise combination of the usage constants in
* {@link Crypt_GPG_SubKey}.
*
* @var integer
*
* @see Crypt_GPG_KeyGenerator::setSubKeyParams()
*/
protected $subKeyUsage = Crypt_GPG_SubKey::USAGE_ENCRYPT;
/**
* Creates a new GnuPG key generator
*
* @param array $options An array of options used to create the object.
* All options are optional and are represented as key-value
* pairs. See Crypt_GPGAbstract::__construct() for more info.
*
* @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
* and cannot be created. This can happen if <kbd>homedir</kbd> is
* not specified, Crypt_GPG is run as the web user, and the web
* user has no home directory. This exception is also thrown if any
* of the options <kbd>publicKeyring</kbd>,
* <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
* specified but the files do not exist or are are not readable.
* This can happen if the user running the Crypt_GPG process (for
* example, the Apache user) does not have permission to read the
* files.
*
* @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
* if no <kbd>binary</kbd> is provided and no suitable binary could
* be found.
*
* @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
* if no <kbd>agent</kbd> is provided and no suitable gpg-agent
* could be found.
*/
public function __construct(array $options = array())
{
parent::__construct($options);
}
/**
* Sets the expiration date of generated keys
*
* @param string|integer $date either a string that may be parsed by
* PHP's strtotime() function, or an integer
* timestamp representing the number of seconds
* since the UNIX epoch. This date must be at
* least one date in the future. Keys that
* expire in the past may not be generated. Use
* an expiration date of 0 for keys that do not
* expire.
*
* @throws InvalidArgumentException if the date is not a valid format, or
* if the date is not at least one day in
* the future, or if the date is greater
* than 2038-01-19T03:14:07.
*
* @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
*/
public function setExpirationDate($date)
{
if (is_int($date) || ctype_digit(strval($date))) {
$expirationDate = intval($date);
} else {
$expirationDate = strtotime($date);
}
if ($expirationDate === false) {
throw new InvalidArgumentException(
sprintf(
'Invalid expiration date format: "%s". Please use a ' .
'format compatible with PHP\'s strtotime().',
$date
)
);
}
if ($expirationDate !== 0 && $expirationDate < time() + 86400) {
throw new InvalidArgumentException(
'Expiration date must be at least a day in the future.'
);
}
// GnuPG suffers from the 2038 bug
if ($expirationDate > 2147483647) {
throw new InvalidArgumentException(
'Expiration date must not be greater than 2038-01-19T03:14:07.'
);
}
$this->expirationDate = $expirationDate;
return $this;
}
/**
* Sets the passphrase of generated keys
*
* @param string $passphrase the passphrase to use for generated keys. Use
* null or an empty string for no passphrase.
*
* @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
*/
public function setPassphrase($passphrase)
{
$this->passphrase = strval($passphrase);
return $this;
}
/**
* Sets the parameters for the primary key of generated key-pairs
*
* @param integer $algorithm the algorithm used by the key. This should be
* one of the Crypt_GPG_SubKey::ALGORITHM_*
* constants.
* @param integer $size optional. The size of the key. Different
* algorithms have different size requirements.
* If not specified, the default size for the
* specified algorithm will be used. If an
* invalid key size is used, GnuPG will do its
* best to round it to a valid size.
* @param integer $usage optional. A bitwise combination of key usages.
* If not specified, the primary key will be used
* only to sign and certify. This is the default
* behavior of GnuPG in interactive mode. Use
* the Crypt_GPG_SubKey::USAGE_* constants here.
* The primary key may be used to certify even
* if the certify usage is not specified.
*
* @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
*/
public function setKeyParams($algorithm, $size = 0, $usage = 0)
{
$algorithm = intval($algorithm);
if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC) {
throw new Crypt_GPG_InvalidKeyParamsException(
'Primary key algorithm must be capable of signing. The ' .
'Elgamal algorithm can only encrypt.',
0,
$algorithm,
$size,
$usage
);
}
if ($size != 0) {
$size = intval($size);
}
if ($usage != 0) {
$usage = intval($usage);
}
$usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;
if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
&& ($usage & $usageEncrypt) === $usageEncrypt
) {
throw new Crypt_GPG_InvalidKeyParamsException(
'The DSA algorithm is not capable of encrypting. Please ' .
'specify a different algorithm or do not include encryption ' .
'as a usage for the primary key.',
0,
$algorithm,
$size,
$usage
);
}
$this->keyAlgorithm = $algorithm;
if ($size != 0) {
$this->keySize = $size;
}
if ($usage != 0) {
$this->keyUsage = $usage;
}
return $this;
}
/**
* Sets the parameters for the sub-key of generated key-pairs
*
* @param integer $algorithm the algorithm used by the key. This should be
* one of the Crypt_GPG_SubKey::ALGORITHM_*
* constants.
* @param integer $size optional. The size of the key. Different
* algorithms have different size requirements.
* If not specified, the default size for the
* specified algorithm will be used. If an
* invalid key size is used, GnuPG will do its
* best to round it to a valid size.
* @param integer $usage optional. A bitwise combination of key usages.
* If not specified, the sub-key will be used
* only to encrypt. This is the default behavior
* of GnuPG in interactive mode. Use the
* Crypt_GPG_SubKey::USAGE_* constants here.
*
* @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
*/
public function setSubKeyParams($algorithm, $size = '', $usage = 0)
{
$algorithm = intval($algorithm);
if ($size != 0) {
$size = intval($size);
}
if ($usage != 0) {
$usage = intval($usage);
}
$usageSign = Crypt_GPG_SubKey::USAGE_SIGN;
if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC
&& ($usage & $usageSign) === $usageSign
) {
throw new Crypt_GPG_InvalidKeyParamsException(
'The Elgamal algorithm is not capable of signing. Please ' .
'specify a different algorithm or do not include signing ' .
'as a usage for the sub-key.',
0,
$algorithm,
$size,
$usage
);
}
$usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;
if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
&& ($usage & $usageEncrypt) === $usageEncrypt
) {
throw new Crypt_GPG_InvalidKeyParamsException(
'The DSA algorithm is not capable of encrypting. Please ' .
'specify a different algorithm or do not include encryption ' .
'as a usage for the sub-key.',
0,
$algorithm,
$size,
$usage
);
}
$this->subKeyAlgorithm = $algorithm;
if ($size != 0) {
$this->subKeySize = $size;
}
if ($usage != 0) {
$this->subKeyUsage = $usage;
}
return $this;
}
/**
* Generates a new key-pair in the current keyring
*
* Secure key generation requires true random numbers, and as such can be
* solw. If the operating system runs out of entropy, key generation will
* block until more entropy is available.
*
* If quick key generation is important, a hardware entropy generator, or
* an entropy gathering daemon may be installed. For example,
* administrators of Debian systems may want to install the 'randomsound'
* package.
*
* @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId}
* object, or a string containing
* the name of the user id.
* @param string $email optional. If <i>$name</i> is
* specified as a string, this is
* the email address of the user id.
* @param string $comment optional. If <i>$name</i> is
* specified as a string, this is
* the comment of the user id.
*
* @return Crypt_GPG_Key the newly generated key.
*
* @throws Crypt_GPG_KeyNotCreatedException if the key parameters are
* incorrect, if an unknown error occurs during key generation, or
* if the newly generated key is not found in the keyring.
*
* @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
* Use the <kbd>debug</kbd> option and file a bug report if these
* exceptions occur.
*/
public function generateKey($name, $email = '', $comment = '')
{
$handle = uniqid('key', true);
$userId = $this->getUserId($name, $email, $comment);
$keyParams = array(
'Key-Type' => $this->keyAlgorithm,
'Key-Length' => $this->keySize,
'Key-Usage' => $this->getUsage($this->keyUsage),
'Subkey-Type' => $this->subKeyAlgorithm,
'Subkey-Length' => $this->subKeySize,
'Subkey-Usage' => $this->getUsage($this->subKeyUsage),
'Handle' => $handle,
);
if ($this->expirationDate != 0) {
// GnuPG only accepts granularity of days
$expirationDate = date('Y-m-d', $this->expirationDate);
$keyParams['Expire-Date'] = $expirationDate;
}
if (strlen($this->passphrase)) {
$keyParams['Passphrase'] = $this->passphrase;
}
$name = $userId->getName();
$email = $userId->getEmail();
$comment = $userId->getComment();
if (strlen($name) > 0) {
$keyParams['Name-Real'] = $name;
}
if (strlen($email) > 0) {
$keyParams['Name-Email'] = $email;
}
if (strlen($comment) > 0) {
$keyParams['Name-Comment'] = $comment;
}
$keyParamsFormatted = array();
foreach ($keyParams as $name => $value) {
$keyParamsFormatted[] = $name . ': ' . $value;
}
// This is required in GnuPG 2.1
if (!strlen($this->passphrase)) {
$keyParamsFormatted[] = '%no-protection';
}
$input = implode("\n", $keyParamsFormatted) . "\n%commit\n";
$this->engine->reset();
$this->engine->setProcessData('Handle', $handle);
$this->engine->setInput($input);
$this->engine->setOutput($output);
$this->engine->setOperation('--gen-key', array('--batch'));
try {
$this->engine->run();
} catch (Crypt_GPG_InvalidKeyParamsException $e) {
switch ($this->engine->getProcessData('LineNumber')) {
case 1:
throw new Crypt_GPG_InvalidKeyParamsException(
'Invalid primary key algorithm specified.',
0,
$this->keyAlgorithm,
$this->keySize,
$this->keyUsage
);
case 4:
throw new Crypt_GPG_InvalidKeyParamsException(
'Invalid sub-key algorithm specified.',
0,
$this->subKeyAlgorithm,
$this->subKeySize,
$this->subKeyUsage
);
default:
throw $e;
}
}
$fingerprint = $this->engine->getProcessData('KeyCreated');
$keys = $this->_getKeys($fingerprint);
if (count($keys) === 0) {
throw new Crypt_GPG_KeyNotCreatedException(
sprintf(
'Newly created key "%s" not found in keyring.',
$fingerprint
)
);
}
return $keys[0];
}
/**
* Builds a GnuPG key usage string suitable for key generation
*
* See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on the key usage format.
*
* @param integer $usage a bitwise combination of the key usages. This is
* a combination of the Crypt_GPG_SubKey::USAGE_*
* constants.
*
* @return string the key usage string.
*/
protected function getUsage($usage)
{
$map = array(
Crypt_GPG_SubKey::USAGE_ENCRYPT => 'encrypt',
Crypt_GPG_SubKey::USAGE_SIGN => 'sign',
Crypt_GPG_SubKey::USAGE_CERTIFY => 'cert',
Crypt_GPG_SubKey::USAGE_AUTHENTICATION => 'auth',
);
// cert is always used for primary keys and does not need to be
// specified
$usage &= ~Crypt_GPG_SubKey::USAGE_CERTIFY;
$usageArray = array();
foreach ($map as $key => $value) {
if (($usage & $key) === $key) {
$usageArray[] = $value;
}
}
return implode(',', $usageArray);
}
/**
* Gets a user id object from parameters
*
* @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId}
* object, or a string containing
* the name of the user id.
* @param string $email optional. If <i>$name</i> is
* specified as a string, this is
* the email address of the user id.
* @param string $comment optional. If <i>$name</i> is
* specified as a string, this is
* the comment of the user id.
*
* @return Crypt_GPG_UserId a user id object for the specified parameters.
*/
protected function getUserId($name, $email = '', $comment = '')
{
if ($name instanceof Crypt_GPG_UserId) {
$userId = $name;
} else {
$userId = new Crypt_GPG_UserId();
$userId->setName($name)->setEmail($email)->setComment($comment);
}
return $userId;
}
}
+764
View File
@@ -0,0 +1,764 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class implementing automatic pinentry for gpg-agent
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* CLI user-interface and parser.
*/
require_once 'Console/CommandLine.php';
/**
* A command-line dummy pinentry program for use with gpg-agent and Crypt_GPG
*
* This pinentry receives passphrases through en environment variable and
* automatically enters the PIN in response to gpg-agent requests. No user-
* interaction required.
*
* The pinentry can be run independently for testing and debugging with the
* following syntax:
*
* <pre>
* Usage:
* crypt-gpg-pinentry [options]
*
* Options:
* -l log, --log=log Optional location to log pinentry activity.
* -v, --verbose Sets verbosity level. Use multiples for more detail
* (e.g. "-vv").
* -h, --help show this help message and exit
* --version show the program version and exit
* </pre>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
*/
class Crypt_GPG_PinEntry
{
/**
* Verbosity level for showing no output.
*/
const VERBOSITY_NONE = 0;
/**
* Verbosity level for showing error output.
*/
const VERBOSITY_ERRORS = 1;
/**
* Verbosity level for showing all output, including Assuan protocol
* messages.
*/
const VERBOSITY_ALL = 2;
/**
* Length of buffer for reading lines from the Assuan server.
*
* PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
* and buffers the rest so we might as well just read 8192.
*
* Using values other than 8192 also triggers PHP bugs.
*
* @see http://bugs.php.net/bug.php?id=35224
*/
const CHUNK_SIZE = 8192;
/**
* File handle for the input stream
*
* @var resource
*/
protected $stdin = null;
/**
* File handle for the output stream
*
* @var resource
*/
protected $stdout = null;
/**
* File handle for the log file if a log file is used
*
* @var resource
*/
protected $logFile = null;
/**
* Whether or not this pinentry is finished and is exiting
*
* @var boolean
*/
protected $moribund = false;
/**
* Verbosity level
*
* One of:
* - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE},
* - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS}, or
* - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL}
*
* @var integer
*/
protected $verbosity = self::VERBOSITY_NONE;
/**
* The command-line interface parser for this pinentry
*
* @var Console_CommandLine
*
* @see Crypt_GPG_PinEntry::getParser()
*/
protected $parser = null;
/**
* PINs to be entered by this pinentry
*
* An indexed array of associative arrays in the form:
* <code>
* <?php
* array(
* array(
* 'keyId' => $keyId,
* 'passphrase' => $passphrase
* ),
* ...
* );
* ?>
* </code>
*
* This array is parsed from the environment variable
* <kbd>PINENTRY_USER_DATA</kbd>.
*
* @var array
*
* @see Crypt_GPG_PinEntry::initPinsFromENV()
*/
protected $pins = array();
/**
* The PIN currently being requested by the Assuan server
*
* If set, this is an associative array in the form:
* <code>
* <?php
* array(
* 'keyId' => $shortKeyId,
* 'userId' => $userIdString
* );
* ?>
* </code>
*
* @var array|null
*/
protected $currentPin = null;
/**
* Runs this pinentry
*
* @return void
*/
public function __invoke()
{
$this->parser = $this->getCommandLineParser();
try {
$result = $this->parser->parse();
$this->setVerbosity($result->options['verbose']);
$this->setLogFilename($result->options['log']);
$this->connect();
$this->initPinsFromENV();
while (($line = fgets($this->stdin, self::CHUNK_SIZE)) !== false) {
$this->parseCommand(mb_substr($line, 0, -1, '8bit'));
if ($this->moribund) {
break;
}
}
$this->disconnect();
} catch (Console_CommandLineException $e) {
$this->log($e->getMessage() . PHP_EOL, slf::VERBOSITY_ERRORS);
exit(1);
} catch (Exception $e) {
$this->log($e->getMessage() . PHP_EOL, self::VERBOSITY_ERRORS);
$this->log($e->getTraceAsString() . PHP_EOL, self::VERBOSITY_ERRORS);
exit(1);
}
}
/**
* Sets the verbosity of logging for this pinentry
*
* Verbosity levels are:
*
* - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE} - no logging.
* - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS} - log errors only.
* - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL} - log everything, including
* the assuan protocol.
*
* @param integer $verbosity the level of verbosity of this pinentry.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
public function setVerbosity($verbosity)
{
$this->verbosity = (integer)$verbosity;
return $this;
}
/**
* Sets the log file location
*
* @param string $filename the new log filename to use. If an empty string
* is used, file-based logging is disabled.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
public function setLogFilename($filename)
{
if (is_resource($this->logFile)) {
fflush($this->logFile);
fclose($this->logFile);
$this->logFile = null;
}
if ($filename != '') {
if (($this->logFile = fopen($filename, 'w')) === false) {
$this->log(
'Unable to open log file "' . $filename . '" '
. 'for writing.' . PHP_EOL,
self::VERBOSITY_ERRORS
);
exit(1);
} else {
stream_set_write_buffer($this->logFile, 0);
}
}
return $this;
}
/**
* Gets the CLI user-interface definition for this pinentry
*
* Detects whether or not this package is PEAR-installed and appropriately
* locates the XML UI definition.
*
* @return string the location of the CLI user-interface definition XML.
*/
protected function getUIXML()
{
// Find PinEntry config depending on the way how the package is installed
$ds = DIRECTORY_SEPARATOR;
$root = __DIR__ . $ds . '..' . $ds . '..' . $ds;
$paths = array(
'@data-dir@' . $ds . '@package-name@' . $ds . 'data', // PEAR
$root . 'data', // Git
$root . 'data' . $ds . 'Crypt_GPG' . $ds . 'data', // Composer
);
foreach ($paths as $path) {
if (file_exists($path . $ds . 'pinentry-cli.xml')) {
return $path . $ds . 'pinentry-cli.xml';
}
}
}
/**
* Gets the CLI parser for this pinentry
*
* @return Console_CommandLine the CLI parser for this pinentry.
*/
protected function getCommandLineParser()
{
return Console_CommandLine::fromXmlFile($this->getUIXML());
}
/**
* Logs a message at the specified verbosity level
*
* If a log file is used, the message is written to the log. Otherwise,
* the message is sent to STDERR.
*
* @param string $data the message to log.
* @param integer $level the verbosity level above which the message should
* be logged.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function log($data, $level)
{
if ($this->verbosity >= $level) {
if (is_resource($this->logFile)) {
fwrite($this->logFile, $data);
fflush($this->logFile);
} else {
$this->parser->outputter->stderr($data);
}
}
return $this;
}
/**
* Connects this pinentry to the assuan server
*
* Opens I/O streams and sends initial handshake.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function connect()
{
// Binary operations will not work on Windows with PHP < 5.2.6.
$rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
$wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
$this->stdin = fopen('php://stdin', $rb);
$this->stdout = fopen('php://stdout', $wb);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($this->stdin, 0);
}
stream_set_write_buffer($this->stdout, 0);
// initial handshake
$this->send($this->getOK('Crypt_GPG pinentry ready and waiting'));
return $this;
}
/**
* Parses an assuan command and performs the appropriate action
*
* Documentation of the assuan commands for pinentry is limited to
* non-existent. Most of these commands were taken from the C source code
* to gpg-agent and pinentry.
*
* Additional context was provided by using strace -f when calling the
* gpg-agent.
*
* @param string $line the assuan command line to parse
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function parseCommand($line)
{
$this->log('<- ' . $line . PHP_EOL, self::VERBOSITY_ALL);
$parts = explode(' ', $line, 2);
$command = $parts[0];
if (count($parts) === 2) {
$data = $parts[1];
} else {
$data = null;
}
switch ($command) {
case 'SETDESC':
return $this->sendSetDescription($data);
case 'MESSAGE':
return $this->sendMessage();
case 'CONFIRM':
return $this->sendConfirm();
case 'GETINFO':
return $this->sendGetInfo($data);
case 'GETPIN':
return $this->sendGetPin($data);
case 'RESET':
return $this->sendReset();
case 'BYE':
return $this->sendBye();
default:
return $this->sendNotImplementedOK();
}
}
/**
* Initializes the PINs to be entered by this pinentry from the environment
* variable PINENTRY_USER_DATA
*
* The PINs are parsed from a JSON-encoded string.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function initPinsFromENV()
{
if (($userData = getenv('PINENTRY_USER_DATA')) !== false) {
$pins = json_decode($userData, true);
if ($pins === null) {
$this->log(
'-- failed to parse user data' . PHP_EOL,
self::VERBOSITY_ERRORS
);
} else {
$this->pins = $pins;
$this->log(
'-- got user data [not showing passphrases]' . PHP_EOL,
self::VERBOSITY_ALL
);
}
}
return $this;
}
/**
* Disconnects this pinentry from the Assuan server
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function disconnect()
{
$this->log('-- disconnecting' . PHP_EOL, self::VERBOSITY_ALL);
fflush($this->stdout);
fclose($this->stdout);
fclose($this->stdin);
$this->stdin = null;
$this->stdout = null;
$this->log('-- disconnected' . PHP_EOL, self::VERBOSITY_ALL);
if (is_resource($this->logFile)) {
fflush($this->logFile);
fclose($this->logFile);
$this->logFile = null;
}
return $this;
}
/**
* Sends an OK response for a not implemented feature
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendNotImplementedOK()
{
return $this->send($this->getOK());
}
/**
* Parses the currently requested key identifier and user identifier from
* the description passed to this pinentry
*
* @param string $text the raw description sent from gpg-agent.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendSetDescription($text)
{
$text = rawurldecode($text);
$matches = array();
// TODO: handle user id with quotation marks
$exp = '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu';
if (preg_match($exp, $text, $matches) === 1) {
$userId = $matches[1];
$keyId = $matches[2];
if ($this->currentPin === null || $this->currentPin['keyId'] !== $keyId) {
$this->currentPin = array(
'userId' => $userId,
'keyId' => $keyId
);
$this->log(
'-- looking for PIN for ' . $keyId . PHP_EOL,
self::VERBOSITY_ALL
);
}
}
return $this->send($this->getOK());
}
/**
* Tells the assuan server to confirm the operation
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendConfirm()
{
return $this->send($this->getOK());
}
/**
* Tells the assuan server that any requested pop-up messages were confirmed
* by pressing the fake 'close' button
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendMessage()
{
return $this->sendButtonInfo('close');
}
/**
* Sends information about pressed buttons to the assuan server
*
* This is used to fake a user-interface for this pinentry.
*
* @param string $text the button status to send.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendButtonInfo($text)
{
return $this->send('BUTTON_INFO ' . $text . "\n");
}
/**
* Sends the PIN value for the currently requested key
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendGetPin()
{
$foundPin = '';
if (is_array($this->currentPin)) {
$keyIdLength = mb_strlen($this->currentPin['keyId'], '8bit');
// search for the pin
foreach ($this->pins as $_keyId => $pin) {
// Warning: GnuPG 2.1 asks 3 times for passphrase if it is invalid
$keyId = $this->currentPin['keyId'];
$_keyIdLength = mb_strlen($_keyId, '8bit');
// Get last X characters of key identifier to compare
// Most GnuPG versions use 8 characters, but recent ones can use 16,
// We support 8 for backward compatibility
if ($keyIdLength < $_keyIdLength) {
$_keyId = mb_substr($_keyId, -$keyIdLength, $keyIdLength, '8bit');
} else if ($keyIdLength > $_keyIdLength) {
$keyId = mb_substr($keyId, -$_keyIdLength, $_keyIdLength, '8bit');
}
if ($_keyId === $keyId) {
$foundPin = $pin;
break;
}
}
}
return $this
->send($this->getData($foundPin))
->send($this->getOK());
}
/**
* Sends information about this pinentry
*
* @param string $data the information requested by the assuan server.
* Currently only 'pid' is supported. Other requests
* return no information.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendGetInfo($data)
{
$parts = explode(' ', $data, 2);
$command = reset($parts);
switch ($command) {
case 'pid':
return $this->sendGetInfoPID();
default:
return $this->send($this->getOK());
}
return $this;
}
/**
* Sends the PID of this pinentry to the assuan server
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendGetInfoPID()
{
return $this
->send($this->getData(getmypid()))
->send($this->getOK());
}
/**
* Flags this pinentry for disconnection and sends an OK response
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendBye()
{
$return = $this->send($this->getOK('closing connection'));
$this->moribund = true;
return $return;
}
/**
* Resets this pinentry and sends an OK response
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendReset()
{
$this->currentPin = null;
return $this->send($this->getOK());
}
/**
* Gets an OK response to send to the assuan server
*
* @param string $data an optional message to include with the OK response.
*
* @return string the OK response.
*/
protected function getOK($data = null)
{
$return = 'OK';
if ($data) {
$return .= ' ' . $data;
}
return $return . "\n";
}
/**
* Gets data ready to send to the assuan server
*
* Data is appropriately escaped and long lines are wrapped.
*
* @param string $data the data to send to the assuan server.
*
* @return string the properly escaped, formatted data.
*
* @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
*/
protected function getData($data)
{
// Escape data. Only %, \n and \r need to be escaped but other
// values are allowed to be escaped. See
// http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
$data = rawurlencode($data);
$data = $this->getWordWrappedData($data, 'D');
return $data;
}
/**
* Gets a comment ready to send to the assuan server
*
* @param string $data the comment to send to the assuan server.
*
* @return string the properly formatted comment.
*
* @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
*/
protected function getComment($data)
{
return $this->getWordWrappedData($data, '#');
}
/**
* Wraps strings at 1,000 bytes without splitting UTF-8 multibyte
* characters
*
* Each line is prepended with the specified line prefix. Wrapped lines
* are automatically appended with \ characters.
*
* Protocol strings are UTF-8 but maximum line length is 1,000 bytes.
* <kbd>mb_strcut()</kbd> is used so we can limit line length by bytes
* and not split characters across multiple lines.
*
* @param string $data the data to wrap.
* @param string $prefix a single character to use as the line prefix. For
* example, 'D' or '#'.
*
* @return string the word-wrapped, prefixed string.
*
* @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
*/
protected function getWordWrappedData($data, $prefix)
{
$lines = array();
do {
if (mb_strlen($data, '8bit') > 997) {
$line = $prefix . ' ' . mb_strcut($data, 0, 996, 'utf-8') . "\\\n";
$lines[] = $line;
$lineLength = mb_strlen($line, '8bit') - 1;
$dataLength = mb_substr($data, '8bit');
$data = mb_substr(
$data,
$lineLength,
$dataLength - $lineLength,
'8bit'
);
} else {
$lines[] = $prefix . ' ' . $data . "\n";
$data = '';
}
} while ($data != '');
return implode('', $lines);
}
/**
* Sends raw data to the assuan server
*
* @param string $data the data to send.
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function send($data)
{
$this->log('-> ' . $data, self::VERBOSITY_ALL);
fwrite($this->stdout, $data);
fflush($this->stdout);
return $this;
}
}
@@ -0,0 +1,128 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* A class for monitoring and terminating processes
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* A class for monitoring and terminating processes by PID
*
* This is used to safely terminate the gpg-agent for GnuPG 2.x. This class
* is limited in its abilities and can only check if a PID is running and
* send a PID SIGTERM.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_ProcessControl
{
/**
* The PID (process identifier) being monitored
*
* @var integer
*/
protected $pid;
/**
* Creates a new process controller from the given PID (process identifier)
*
* @param integer $pid the PID (process identifier).
*/
public function __construct($pid)
{
$this->pid = $pid;
}
/**
* Gets the PID (process identifier) being controlled
*
* @return integer the PID being controlled.
*/
public function getPid()
{
return $this->pid;
}
/**
* Checks if the process is running
*
* If the <kbd>posix</kbd> extension is available, <kbd>posix_getpgid()</kbd>
* is used. Otherwise <kbd>ps</kbd> is used on UNIX-like systems and
* <kbd>tasklist</kbd> on Windows.
*
* @return boolean true if the process is running, false if not.
*/
public function isRunning()
{
$running = false;
if (function_exists('posix_getpgid')) {
$running = false !== posix_getpgid($this->pid);
} elseif (PHP_OS === 'WINNT') {
$command = 'tasklist /fo csv /nh /fi '
. escapeshellarg('PID eq ' . $this->pid);
$result = exec($command);
$parts = explode(',', $result);
$running = (count($parts) > 1 && trim($parts[1], '"') == $this->pid);
} else {
$result = exec('ps -p ' . escapeshellarg($this->pid) . ' -o pid=');
$running = (trim($result) == $this->pid);
}
return $running;
}
/**
* Ends the process gracefully
*
* The signal SIGTERM is sent to the process. The gpg-agent process will
* end gracefully upon receiving the SIGTERM signal. Upon 3 consecutive
* SIGTERM signals the gpg-agent will forcefully shut down.
*
* If the <kbd>posix</kbd> extension is available, <kbd>posix_kill()</kbd>
* is used. Otherwise <kbd>kill</kbd> is used on UNIX-like systems and
* <kbd>taskkill</kbd> is used in Windows.
*
* @return void
*/
public function terminate()
{
if (function_exists('posix_kill')) {
posix_kill($this->pid, 15);
} elseif (PHP_OS === 'WINNT') {
exec('taskkill /PID ' . escapeshellarg($this->pid));
} else {
exec('kill -15 ' . escapeshellarg($this->pid));
}
}
}
@@ -0,0 +1,900 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains handler for status and error pipes of GPG process.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* GPG exception classes.
*/
require_once 'Crypt/GPG/Exceptions.php';
/**
* Signature object class definition
*/
require_once 'Crypt/GPG/Signature.php';
/**
* Status/Error handler for GPG process pipes.
*
* This class is used internally by Crypt_GPG_Engine and does not need to be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_ProcessHandler
{
/**
* Engine used to control the GPG subprocess
*
* @var Crypt_GPG_Engine
*/
protected $engine;
/**
* The error code of the current operation
*
* @var integer
*/
protected $errorCode = Crypt_GPG::ERROR_NONE;
/**
* The number of currently needed passphrases
*
* If this is not zero when the GPG command is completed, the error code is
* set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
*
* @var integer
*/
protected $needPassphrase = 0;
/**
* Some data collected while processing the operation
* or set for the operation
*
* @var array
* @see self::setData()
* @see self::getData()
*/
protected $data = array();
/**
* The name of the current operation
*
* @var string
* @see self::setOperation()
*/
protected $operation = null;
/**
* The value of the argument of current operation
*
* @var string
* @see self::setOperation()
*/
protected $operationArg = null;
/**
* Creates a new instance
*
* @param Crypt_GPG_Engine $engine Engine object
*/
public function __construct($engine)
{
$this->engine = $engine;
}
/**
* Sets the operation that is being performed by the engine.
*
* @param string $operation The GPG operation to perform.
*
* @return void
*/
public function setOperation($operation)
{
$op = null;
$opArg = null;
// Regexp matching all GPG "operational" arguments
$regexp = '/--('
. 'version|import|list-public-keys|list-secret-keys'
. '|list-keys|delete-key|delete-secret-key|encrypt|sign|clearsign'
. '|detach-sign|decrypt|verify|export-secret-keys|export|gen-key'
. ')/';
if (strpos($operation, ' ') === false) {
$op = trim($operation, '- ');
} else if (preg_match($regexp, $operation, $matches, PREG_OFFSET_CAPTURE)) {
$op = trim($matches[0][0], '-');
$op_len = $matches[0][1] + mb_strlen($op, '8bit') + 3;
$command = mb_substr($operation, $op_len, null, '8bit');
// we really need the argument if it is a key ID/fingerprint or email
// address se we can use simplified regexp to "revert escapeshellarg()"
if (preg_match('/^[\'"]([a-zA-Z0-9:@._-]+)[\'"]/', $command, $matches)) {
$opArg = $matches[1];
}
}
$this->operation = $op;
$this->operationArg = $opArg;
$this->data['Warnings'] = array();
}
/**
* Handles error values in the status output from GPG
*
* This method is responsible for setting the
* {@link self::$errorCode}. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on GPG's status output.
*
* @param string $line the status line to handle.
*
* @return void
*/
public function handleStatus($line)
{
$tokens = explode(' ', $line);
switch ($tokens[0]) {
case 'NODATA':
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
case 'DECRYPTION_OKAY':
// If the message is encrypted, this is the all-clear signal.
$this->data['DecryptionOkay'] = true;
$this->errorCode = Crypt_GPG::ERROR_NONE;
break;
case 'DELETE_PROBLEM':
if ($tokens[1] == '1') {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
break;
} elseif ($tokens[1] == '2') {
$this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
break;
}
break;
case 'IMPORT_OK':
$this->data['Import']['fingerprint'] = $tokens[2];
if (empty($this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'] = array($tokens[2]);
} else if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'][] = $tokens[2];
}
break;
case 'IMPORT_RES':
$this->data['Import']['public_imported'] = intval($tokens[3]);
$this->data['Import']['public_unchanged'] = intval($tokens[5]);
$this->data['Import']['private_imported'] = intval($tokens[11]);
$this->data['Import']['private_unchanged'] = intval($tokens[12]);
break;
case 'NO_PUBKEY':
case 'NO_SECKEY':
$this->data['ErrorKeyId'] = $tokens[1];
if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE
&& $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE
&& !(
$this->operation == 'decrypt'
&& $tokens[0] == 'NO_PUBKEY'
&& !empty($this->data['IgnoreVerifyErrors'])
)
) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
// note: this message is also received if there are multiple
// recipients and a previous key had a correct passphrase.
$this->data['MissingKeys'][$tokens[1]] = $tokens[1];
// @FIXME: remove missing passphrase registered in ENC_TO handler
// This is for GnuPG 2.1
unset($this->data['MissingPassphrases'][$tokens[1]]);
break;
case 'KEY_CONSIDERED':
// In GnuPG 2.1.x exporting/importing a secret key requires passphrase
// However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667
// So, handling KEY_CONSIDERED and GET_HIDDEN is needed.
if (!array_key_exists('KeyConsidered', $this->data)) {
$this->data['KeyConsidered'] = $tokens[1];
}
break;
case 'USERID_HINT':
// remember the user id for pretty exception messages
// GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]"
$keyId = $tokens[1];
if (strcspn($keyId, '0')) {
$username = implode(' ', array_splice($tokens, 2));
$this->data['BadPassphrases'][$keyId] = $username;
}
break;
case 'ENC_TO':
// Now we know the message is encrypted. Set flag to check if
// decryption succeeded.
$this->data['DecryptionOkay'] = false;
// this is the new key message
$this->data['CurrentSubKeyId'] = $keyId = $tokens[1];
// For some reason in GnuPG 2.1.11 I get only ENC_TO and no
// NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT
// This is not needed for GnuPG 2.1.15
if (!empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
} else {
$passphrases = array();
}
// @TODO: Get user name/email
$this->data['BadPassphrases'][$keyId] = $keyId;
if (empty($passphrases) || empty($passphrases[$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
break;
case 'GOOD_PASSPHRASE':
// if we got a good passphrase, remove the key from the list of
// bad passphrases.
if (isset($this->data['CurrentSubKeyId'])) {
unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]);
unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]);
}
$this->needPassphrase--;
break;
case 'BAD_PASSPHRASE':
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 'MISSING_PASSPHRASE':
if (isset($this->data['CurrentSubKeyId'])) {
$this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]
= $this->data['CurrentSubKeyId'];
}
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 'GET_HIDDEN':
if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) {
$tokens[1] = $this->data['KeyConsidered'];
} else {
break;
}
// no break
case 'NEED_PASSPHRASE':
$passphrase = $this->getPin($tokens[1]);
$this->engine->sendCommand($passphrase);
if ($passphrase === '') {
$this->needPassphrase++;
}
break;
case 'SIG_CREATED':
$this->data['SigCreated'] = $line;
break;
case 'SIG_ID':
// note: signature id comes before new signature line and may not
// exist for some signature types
$this->data['SignatureId'] = $tokens[1];
break;
case 'EXPSIG':
case 'EXPKEYSIG':
case 'REVKEYSIG':
case 'BADSIG':
case 'ERRSIG':
$this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
// no break
case 'GOODSIG':
$signature = new Crypt_GPG_Signature();
// if there was a signature id, set it on the new signature
if (!empty($this->data['SignatureId'])) {
$signature->setId($this->data['SignatureId']);
$this->data['SignatureId'] = '';
}
// Detect whether fingerprint or key id was returned and set
// signature values appropriately. Key ids are strings of either
// 16 or 8 hexadecimal characters. Fingerprints are strings of 40
// hexadecimal characters. The key id is the last 16 characters of
// the key fingerprint.
if (mb_strlen($tokens[1], '8bit') > 16) {
$signature->setKeyFingerprint($tokens[1]);
$signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit'));
} else {
$signature->setKeyId($tokens[1]);
}
// get user id string
if ($tokens[0] != 'ERRSIG') {
$string = implode(' ', array_splice($tokens, 2));
$string = rawurldecode($string);
$signature->setUserId(Crypt_GPG_UserId::parse($string));
}
$this->data['Signatures'][] = $signature;
break;
case 'VALIDSIG':
if (empty($this->data['Signatures'])) {
break;
}
$signature = end($this->data['Signatures']);
$signature->setValid(true);
$signature->setKeyFingerprint($tokens[1]);
if (strpos($tokens[3], 'T') === false) {
$signature->setCreationDate($tokens[3]);
} else {
$signature->setCreationDate(strtotime($tokens[3]));
}
if (array_key_exists(4, $tokens)) {
if (strpos($tokens[4], 'T') === false) {
$signature->setExpirationDate($tokens[4]);
} else {
$signature->setExpirationDate(strtotime($tokens[4]));
}
}
break;
case 'KEY_CREATED':
if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) {
$this->data['KeyCreated'] = $tokens[2];
}
break;
case 'KEY_NOT_CREATED':
if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED;
}
break;
case 'PROGRESS':
// todo: at some point, support reporting status async
break;
// GnuPG 2.1 uses FAILURE and ERROR responses
case 'FAILURE':
case 'ERROR':
$errnum = (int) $tokens[2];
$source = $errnum >> 24;
$errcode = $errnum & 0xFFFFFF;
switch ($errcode) {
case 11: // bad passphrase
case 87: // bad PIN
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 177: // no passphrase
case 178: // no PIN
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 58:
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
}
break;
}
}
/**
* Handles error values in the error output from GPG
*
* This method is responsible for setting the
* {@link Crypt_GPG_Engine::$_errorCode}.
*
* @param string $line the error line to handle.
*
* @return void
*/
public function handleError($line)
{
if (stripos($line, 'gpg: WARNING: ') !== false) {
$this->data['Warnings'][] = substr($line, 14);
}
if ($this->errorCode !== Crypt_GPG::ERROR_NONE) {
return;
}
$pattern = '/no valid OpenPGP data found/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
return;
}
$pattern = '/No secret key|secret key not available/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
return;
}
$pattern = '/No public key|public key not found/';
if (preg_match($pattern, $line) === 1) {
if ($this->operation == 'decrypt' && !empty($this->data['IgnoreVerifyErrors'])) {
return;
}
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
return;
}
$pattern = '/can\'t (?:access|open) `(.*?)\'/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->data['ErrorFilename'] = $matches[1];
$this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
return;
}
// GnuPG 2.1: It should return MISSING_PASSPHRASE, but it does not
// we have to detect it this way. This happens e.g. on private key import
$pattern = '/key ([0-9A-F]+).* (Bad|No) passphrase/';
if (preg_match($pattern, $line, $matches) === 1) {
$keyId = $matches[1];
// @TODO: Get user name/email
if (empty($this->data['BadPassphrases'][$keyId])) {
$this->data['BadPassphrases'][$keyId] = $keyId;
}
if ($matches[2] == 'Bad') {
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
} else {
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
if (empty($this->data['MissingPassphrases'][$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
}
return;
}
if ($this->operation == 'gen-key') {
$pattern = '/:([0-9]+): invalid algorithm$/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->errorCode = Crypt_GPG::ERROR_BAD_KEY_PARAMS;
$this->data['LineNumber'] = intval($matches[1]);
}
}
}
/**
* On error throws exception
*
* @param int $exitcode GPG process exit code
*
* @return void
* @throws Crypt_GPG_Exception
*/
public function throwException($exitcode = 0)
{
if ($exitcode > 0 && $this->errorCode === Crypt_GPG::ERROR_NONE) {
$this->errorCode = $this->setErrorCode($exitcode);
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
return;
}
$code = $this->errorCode;
$note = "Please use the 'debug' option when creating the Crypt_GPG " .
"object, and file a bug report at " . Crypt_GPG::BUG_URI;
switch ($this->operation) {
case 'version':
throw new Crypt_GPG_Exception(
'Unknown error getting GnuPG version information. ' . $note,
$code
);
case 'list-secret-keys':
case 'list-public-keys':
case 'list-keys':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
// ignore not found key errors
break;
case Crypt_GPG::ERROR_FILE_PERMISSIONS:
if (!empty($this->data['ErrorFilename'])) {
throw new Crypt_GPG_FileException(
sprintf(
'Error reading GnuPG data file \'%s\'. Check to make ' .
'sure it is readable by the current user.',
$this->data['ErrorFilename']
),
$code,
$this->data['ErrorFilename']
);
}
throw new Crypt_GPG_FileException(
'Error reading GnuPG data file. Check to make sure that ' .
'GnuPG data files are readable by the current user.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error getting keys. ' . $note, $code
);
}
break;
case 'delete-key':
case 'delete-secret-key':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Key not found: ' . $this->operationArg,
$code,
$this->operationArg
);
case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
throw new Crypt_GPG_DeletePrivateKeyException(
'Private key must be deleted before public key can be ' .
'deleted.',
$code,
$this->operationArg
);
default:
throw new Crypt_GPG_Exception(
'Unknown error deleting key. ' . $note, $code
);
}
break;
case 'import':
switch ($code) {
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid GPG key data found.', $code
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot import private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error importing GPG key. ' . $note, $code
);
}
break;
case 'export':
case 'export-secret-keys':
switch ($code) {
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot export private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error exporting a key. ' . $note, $code
);
}
break;
case 'encrypt':
case 'sign':
case 'clearsign':
case 'detach-sign':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Cannot sign data. Private key not found. Import the '.
'private key before trying to sign data.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. Incorrect passphrase provided.', $code
);
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. No passphrase provided.', $code
);
default:
throw new Crypt_GPG_Exception(
"Unknown error {$this->operation}ing data. $note", $code
);
}
break;
case 'verify':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid signature data found.', $code
);
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Public key required for data verification not in keyring.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
default:
throw new Crypt_GPG_Exception(
'Unknown error validating signature details. ' . $note,
$code
);
}
break;
case 'decrypt':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
if (!empty($this->data['MissingKeys'])) {
$keyId = reset($this->data['MissingKeys']);
} else {
$keyId = '';
}
throw new Crypt_GPG_KeyNotFoundException(
'Cannot decrypt data. No suitable private key is in the ' .
'keyring. Import a suitable private key before trying to ' .
'decrypt this data.',
$code,
$keyId
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot decrypt data.');
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'Cannot decrypt data. No PGP encrypted data was found in '.
'the provided data.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error decrypting data.', $code
);
}
break;
case 'gen-key':
switch ($code) {
case Crypt_GPG::ERROR_BAD_KEY_PARAMS:
throw new Crypt_GPG_InvalidKeyParamsException(
'Invalid key algorithm specified.', $code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error generating key-pair. ' . $note, $code
);
}
}
}
/**
* Check exit code of the GPG operation.
*
* @param int $exitcode GPG process exit code
*
* @return int Internal error code
*/
protected function setErrorCode($exitcode)
{
if ($this->needPassphrase > 0) {
return Crypt_GPG::ERROR_MISSING_PASSPHRASE;
}
if ($this->operation == 'import') {
return Crypt_GPG::ERROR_NONE;
}
if ($this->operation == 'decrypt' && !empty($this->data['DecryptionOkay'])) {
if (!empty($this->data['IgnoreVerifyErrors'])) {
return Crypt_GPG::ERROR_NONE;
}
if (!empty($this->data['MissingKeys'])) {
return Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
}
return Crypt_GPG::ERROR_UNKNOWN;
}
/**
* Get data from the last process execution.
*
* @param string $name Data element name:
* - SigCreated: The last SIG_CREATED status.
* - KeyConsidered: The last KEY_CONSIDERED status identifier.
* - KeyCreated: The KEY_CREATED status (for specified Handle).
* - Signatures: Signatures data from verification process.
* - LineNumber: Number of the gen-key error line.
* - Import: Result of IMPORT_OK/IMPORT_RES
* - Warnings: An array of all collected GnuPG warnings
*
* @return mixed
*/
public function getData($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
}
/**
* Set data for the process execution.
*
* @param string $name Data element name:
* - Handle: The unique key handle used by this handler
* The key handle is used to track GPG status output
* for a particular key on --gen-key command before
* the key has its own identifier.
* - IgnoreVerifyErrors: Do not throw exceptions
* when signature verification failes because
* of a missing public key.
* @param mixed $value Data element value
*
* @return void
*/
public function setData($name, $value)
{
switch ($name) {
case 'Handle':
$this->data[$name] = strval($value);
break;
case 'IgnoreVerifyErrors':
$this->data[$name] = (bool) $value;
break;
}
}
/**
* Create Crypt_GPG_BadPassphraseException from operation data.
*
* @param int $code Error code
* @param string $message Error message
*
* @return Crypt_GPG_BadPassphraseException
*/
protected function badPassException($code, $message)
{
$badPassphrases = array_diff_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
);
$missingPassphrases = array_intersect_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
);
if (count($badPassphrases) > 0) {
$message .= ' Incorrect passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
if (count($missingPassphrases) > 0) {
$message .= ' No passphrase provided for keys: "' .
implode('", "', $missingPassphrases) . '".';
}
return new Crypt_GPG_BadPassphraseException(
$message,
$code,
$badPassphrases,
$missingPassphrases
);
}
/**
* Get registered passphrase for specified key.
*
* @param string $key Key identifier
*
* @return string Passphrase
*/
protected function getPin($key)
{
$passphrase = '';
$keyIdLength = mb_strlen($key, '8bit');
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $_keyId => $pass) {
$keyId = $key;
$_keyIdLength = mb_strlen($_keyId, '8bit');
// Get last X characters of key identifier to compare
if ($keyIdLength < $_keyIdLength) {
$_keyId = mb_substr($_keyId, -$keyIdLength, null, '8bit');
} else if ($keyIdLength > $_keyIdLength) {
$keyId = mb_substr($keyId, -$_keyIdLength, null, '8bit');
}
if ($_keyId === $keyId) {
$passphrase = $pass;
break;
}
}
}
return $passphrase;
}
}
+370
View File
@@ -0,0 +1,370 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* A class representing GPG signatures
*
* This file contains a data class representing a GPG signature.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
/**
* A class for GPG signature information
*
* This class is used to store the results of the Crypt_GPG::verify() method.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::verify()
*/
class Crypt_GPG_Signature
{
/**
* A base64-encoded string containing a unique id for this signature if
* this signature has been verified as ok
*
* This id is used to prevent replay attacks and is not present for all
* types of signatures.
*
* @var string
*/
private $_id = '';
/**
* The fingerprint of the key used to create the signature
*
* @var string
*/
private $_keyFingerprint = '';
/**
* The id of the key used to create the signature
*
* @var string
*/
private $_keyId = '';
/**
* The creation date of this signature
*
* This is a Unix timestamp.
*
* @var integer
*/
private $_creationDate = 0;
/**
* The expiration date of the signature
*
* This is a Unix timestamp. If this signature does not expire, this will
* be zero.
*
* @var integer
*/
private $_expirationDate = 0;
/**
* The user id associated with this signature
*
* @var Crypt_GPG_UserId
*/
private $_userId = null;
/**
* Whether or not this signature is valid
*
* @var boolean
*/
private $_isValid = false;
/**
* Creates a new signature
*
* Signatures can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the unique id of this signature.
* - <kbd>string fingerprint</kbd> - the fingerprint of the key used to
* create the signature. The fingerprint
* should not contain formatting
* characters.
* - <kbd>string keyId</kbd> - the id of the key used to create the
* the signature.
* - <kbd>integer creation</kbd> - the date the signature was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the signature expired. This
* is a UNIX timestamp. If the signature
* does not expire, use 0.
* - <kbd>boolean valid</kbd> - whether or not the signature is valid.
* - <kbd>string userId</kbd> - the user id associated with the
* signature. This may also be a
* {@link Crypt_GPG_UserId} object.
*
* @param Crypt_GPG_Signature|array|null $signature Either an existing signature object,
* which is copied; or an array
* of initial values.
*/
public function __construct($signature = null)
{
// copy from object
if ($signature instanceof Crypt_GPG_Signature) {
$this->_id = $signature->_id;
$this->_keyFingerprint = $signature->_keyFingerprint;
$this->_keyId = $signature->_keyId;
$this->_creationDate = $signature->_creationDate;
$this->_expirationDate = $signature->_expirationDate;
$this->_isValid = $signature->_isValid;
if ($signature->_userId instanceof Crypt_GPG_UserId) {
$this->_userId = clone $signature->_userId;
}
}
// initialize from array
if (is_array($signature)) {
if (array_key_exists('id', $signature)) {
$this->setId($signature['id']);
}
if (array_key_exists('fingerprint', $signature)) {
$this->setKeyFingerprint($signature['fingerprint']);
}
if (array_key_exists('keyId', $signature)) {
$this->setKeyId($signature['keyId']);
}
if (array_key_exists('creation', $signature)) {
$this->setCreationDate($signature['creation']);
}
if (array_key_exists('expiration', $signature)) {
$this->setExpirationDate($signature['expiration']);
}
if (array_key_exists('valid', $signature)) {
$this->setValid($signature['valid']);
}
if (array_key_exists('userId', $signature)) {
$userId = new Crypt_GPG_UserId($signature['userId']);
$this->setUserId($userId);
}
}
}
/**
* Gets the id of this signature
*
* @return string a base64-encoded string containing a unique id for this
* signature. This id is used to prevent replay attacks and
* is not present for all types of signatures.
*/
public function getId()
{
return $this->_id;
}
/**
* Gets the fingerprint of the key used to create this signature
*
* @return string the fingerprint of the key used to create this signature.
*/
public function getKeyFingerprint()
{
return $this->_keyFingerprint;
}
/**
* Gets the id of the key used to create this signature
*
* Whereas the fingerprint of the signing key may not always be available
* (for example if the signature is bad), the id should always be
* available.
*
* @return string the id of the key used to create this signature.
*/
public function getKeyId()
{
return $this->_keyId;
}
/**
* Gets the creation date of this signature
*
* @return integer the creation date of this signature. This is a Unix
* timestamp.
*/
public function getCreationDate()
{
return $this->_creationDate;
}
/**
* Gets the expiration date of the signature
*
* @return integer the expiration date of this signature. This is a Unix
* timestamp. If this signature does not expire, this will
* be zero.
*/
public function getExpirationDate()
{
return $this->_expirationDate;
}
/**
* Gets the user id associated with this signature
*
* @return Crypt_GPG_UserId the user id associated with this signature.
*/
public function getUserId()
{
return $this->_userId;
}
/**
* Gets whether or no this signature is valid
*
* @return boolean true if this signature is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
/**
* Sets the id of this signature
*
* @param string $id a base64-encoded string containing a unique id for
* this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*
* @see Crypt_GPG_Signature::getId()
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
/**
* Sets the key fingerprint of this signature
*
* @param string $fingerprint the key fingerprint of this signature. This
* is the fingerprint of the primary key used to
* create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyFingerprint($fingerprint)
{
$this->_keyFingerprint = strval($fingerprint);
return $this;
}
/**
* Sets the key id of this signature
*
* @param string $id the key id of this signature. This is the id of the
* primary key used to create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyId($id)
{
$this->_keyId = strval($id);
return $this;
}
/**
* Sets the creation date of this signature
*
* @param integer $creationDate the creation date of this signature. This
* is a Unix timestamp.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
$this->_creationDate = intval($creationDate);
return $this;
}
/**
* Sets the expiration date of this signature
*
* @param integer $expirationDate the expiration date of this signature.
* This is a Unix timestamp. Specify zero if
* this signature does not expire.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
$this->_expirationDate = intval($expirationDate);
return $this;
}
/**
* Sets the user id associated with this signature
*
* @param Crypt_GPG_UserId $userId the user id associated with this
* signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setUserId(Crypt_GPG_UserId $userId)
{
$this->_userId = $userId;
return $this;
}
/**
* Sets whether or not this signature is valid
*
* @param boolean $isValid true if this signature is valid and false if it
* is not.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
}
@@ -0,0 +1,225 @@
<?php
/**
* Part of Crypt_GPG
*
* @category Encryption
* @package Crypt_GPG
* @author Christian Weiske <cweiske@php.net>
* @copyright 2015 PEAR
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
* @link http://www.gnupg.org/
*/
/**
* Information about a recently created signature.
*
* @category Encryption
* @package Crypt_GPG
* @author Christian Weiske <cweiske@php.net>
* @copyright 2015 PEAR
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
* @link http://www.gnupg.org/
*/
class Crypt_GPG_SignatureCreationInfo
{
/**
* One of the three signature types:
* - {@link Crypt_GPG::SIGN_MODE_NORMAL}
* - {@link Crypt_GPG::SIGN_MODE_CLEAR}
* - {@link Crypt_GPG::SIGN_MODE_DETACHED}
*
* @var integer
*/
protected $mode;
/**
* Public Key algorithm
*
* @var integer
*/
protected $pkAlgorithm;
/**
* Algorithm to hash the data
*
* @see RFC 2440 / 9.4. Hash Algorithm
* @var integer
*/
protected $hashAlgorithm;
/**
* OpenPGP signature class
*
* @var mixed
*/
protected $class;
/**
* Unix timestamp when the signature was created
*
* @var integer
*/
protected $timestamp;
/**
* Key fingerprint
*
* @var string
*/
protected $keyFingerprint;
/**
* If the line given to the constructor was valid
*
* @var boolean
*/
protected $valid;
/**
* Names for the hash algorithm IDs.
*
* Names taken from RFC 3156, without the leading "pgp-".
*
* @see RFC 2440 / 9.4. Hash Algorithm
* @see RFC 3156 / 5. OpenPGP signed data
* @var array
*/
protected static $hashAlgorithmNames = array(
1 => 'md5',
2 => 'sha1',
3 => 'ripemd160',
5 => 'md2',
6 => 'tiger192',
7 => 'haval-5-160',
8 => 'sha256',
9 => 'sha384',
10 => 'sha512',
11 => 'sha224',
);
/**
* Parse a SIG_CREATED line from gnupg
*
* @param string $sigCreatedLine Line beginning with "SIG_CREATED "
*/
public function __construct($sigCreatedLine = null)
{
if ($sigCreatedLine === null) {
$this->valid = false;
return;
}
$parts = explode(' ', $sigCreatedLine);
if (count($parts) !== 7) {
$this->valid = false;
return;
}
list(
$title, $mode, $pkAlgorithm, $hashAlgorithm,
$class, $timestamp, $keyFingerprint
) = $parts;
switch (strtoupper($mode[0])) {
case 'D':
$this->mode = Crypt_GPG::SIGN_MODE_DETACHED;
break;
case 'C':
$this->mode = Crypt_GPG::SIGN_MODE_CLEAR;
break;
case 'S':
$this->mode = Crypt_GPG::SIGN_MODE_NORMAL;
break;
}
$this->pkAlgorithm = (int) $pkAlgorithm;
$this->hashAlgorithm = (int) $hashAlgorithm;
$this->class = $class;
if (is_numeric($timestamp)) {
$this->timestamp = (int) $timestamp;
} else {
$this->timestamp = strtotime($timestamp);
}
$this->keyFingerprint = $keyFingerprint;
$this->valid = true;
}
/**
* Get the signature type
* - {@link Crypt_GPG::SIGN_MODE_NORMAL}
* - {@link Crypt_GPG::SIGN_MODE_CLEAR}
* - {@link Crypt_GPG::SIGN_MODE_DETACHED}
*
* @return integer
*/
public function getMode()
{
return $this->mode;
}
/**
* Return the public key algorithm used.
*
* @return integer
*/
public function getPkAlgorithm()
{
return $this->pkAlgorithm;
}
/**
* Return the hash algorithm used to hash the data to sign.
*
* @return integer
*/
public function getHashAlgorithm()
{
return $this->hashAlgorithm;
}
/**
* Get a name for the used hashing algorithm.
*
* @return string|null
*/
public function getHashAlgorithmName()
{
if (!isset(self::$hashAlgorithmNames[$this->hashAlgorithm])) {
return null;
}
return self::$hashAlgorithmNames[$this->hashAlgorithm];
}
/**
* Return the timestamp at which the signature was created
*
* @return integer
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* Return the key's fingerprint
*
* @return string
*/
public function getKeyFingerprint()
{
return $this->keyFingerprint;
}
/**
* Tell if the fingerprint line given to the constructor was valid
*
* @return boolean
*/
public function isValid()
{
return $this->valid;
}
}
+661
View File
@@ -0,0 +1,661 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG sub-keys and constants for GPG algorithms
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* A class for GPG sub-key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. Sub-key objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getSubKeys()
*/
class Crypt_GPG_SubKey
{
/**
* RSA encryption algorithm.
*/
const ALGORITHM_RSA = 1;
/**
* Elgamal encryption algorithm (encryption only).
*/
const ALGORITHM_ELGAMAL_ENC = 16;
/**
* DSA encryption algorithm (sometimes called DH, sign only).
*/
const ALGORITHM_DSA = 17;
/**
* Elgamal encryption algorithm (signage and encryption - should not be
* used).
*/
const ALGORITHM_ELGAMAL_ENC_SGN = 20;
/**
* Key can be used to encrypt
*/
const USAGE_ENCRYPT = 1;
/**
* Key can be used to sign
*/
const USAGE_SIGN = 2;
/**
* Key can be used to certify other keys
*/
const USAGE_CERTIFY = 4;
/**
* Key can be used for authentication
*/
const USAGE_AUTHENTICATION = 8;
/**
* The id of this sub-key
*
* @var string
*/
private $_id = '';
/**
* The algorithm used to create this sub-key
*
* The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants.
*
* @var integer
*/
private $_algorithm = 0;
/**
* The fingerprint of this sub-key
*
* @var string
*/
private $_fingerprint = '';
/**
* Length of this sub-key in bits
*
* @var integer
*/
private $_length = 0;
/**
* Date this sub-key was created
*
* This is a Unix timestamp.
*
* @var DateTime
*/
private $_creationDate;
/**
* Date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* null.
*
* @var DateTime
*/
private $_expirationDate;
/**
* Contains usage flags of this sub-key
*
* @var int
*/
private $_usage = 0;
/**
* Whether or not the private key for this sub-key exists in the keyring
*
* @var boolean
*/
private $_hasPrivate = false;
/**
* Whether or not this sub-key is revoked
*
* @var boolean
*/
private $_isRevoked = false;
/**
* Creates a new sub-key object
*
* Sub-keys can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the key id of the sub-key.
* - <kbd>integer algorithm</kbd> - the encryption algorithm of the
* sub-key.
* - <kbd>string fingerprint</kbd> - the fingerprint of the sub-key. The
* fingerprint should not contain
* formatting characters.
* - <kbd>integer length</kbd> - the length of the sub-key in bits.
* - <kbd>integer creation</kbd> - the date the sub-key was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the sub-key expires. This
* is a UNIX timestamp. If the sub-key
* does not expire, use 0.
* - <kbd>boolean canSign</kbd> - whether or not the sub-key can be
* used to sign data.
* - <kbd>boolean canEncrypt</kbd> - whether or not the sub-key can be
* used to encrypt data.
* - <kbd>integer usage</kbd> - the sub-key usage flags
* - <kbd>boolean hasPrivate</kbd> - whether or not the private key for
* the sub-key exists in the keyring.
* - <kbd>boolean isRevoked</kbd> - whether or not this sub-key is
* revoked.
*
* @param Crypt_GPG_SubKey|string|array|null $key Either an existing sub-key object,
* which is copied; a sub-key string,
* which is parsed; or an array
* of initial values.
*/
public function __construct($key = null)
{
// parse from string
if (is_string($key)) {
$key = self::parse($key);
}
// copy from object
if ($key instanceof Crypt_GPG_SubKey) {
$this->_id = $key->_id;
$this->_algorithm = $key->_algorithm;
$this->_fingerprint = $key->_fingerprint;
$this->_length = $key->_length;
$this->_creationDate = $key->_creationDate;
$this->_expirationDate = $key->_expirationDate;
$this->_usage = $key->_usage;
$this->_hasPrivate = $key->_hasPrivate;
$this->_isRevoked = $key->_isRevoked;
}
// initialize from array
if (is_array($key)) {
if (array_key_exists('id', $key)) {
$this->setId($key['id']);
}
if (array_key_exists('algorithm', $key)) {
$this->setAlgorithm($key['algorithm']);
}
if (array_key_exists('fingerprint', $key)) {
$this->setFingerprint($key['fingerprint']);
}
if (array_key_exists('length', $key)) {
$this->setLength($key['length']);
}
if (array_key_exists('creation', $key)) {
$this->setCreationDate($key['creation']);
}
if (array_key_exists('expiration', $key)) {
$this->setExpirationDate($key['expiration']);
}
if (array_key_exists('usage', $key)) {
$this->setUsage($key['usage']);
}
if (array_key_exists('canSign', $key)) {
$this->setCanSign($key['canSign']);
}
if (array_key_exists('canEncrypt', $key)) {
$this->setCanEncrypt($key['canEncrypt']);
}
if (array_key_exists('hasPrivate', $key)) {
$this->setHasPrivate($key['hasPrivate']);
}
if (array_key_exists('isRevoked', $key)) {
$this->setRevoked($key['isRevoked']);
}
}
}
/**
* Gets the id of this sub-key
*
* @return string the id of this sub-key.
*/
public function getId()
{
return $this->_id;
}
/**
* Gets the algorithm used by this sub-key
*
* The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_*
* constants.
*
* @return integer the algorithm used by this sub-key.
*/
public function getAlgorithm()
{
return $this->_algorithm;
}
/**
* Gets the creation date of this sub-key
*
* This is a Unix timestamp. Warning: On 32-bit systems it returns
* invalid value for dates after 2038-01-19. Use getCreationDateTime().
*
* @return integer the creation date of this sub-key.
*/
public function getCreationDate()
{
return $this->_creationDate ? (int) $this->_creationDate->format('U') : 0;
}
/**
* Gets the creation date-time (UTC) of this sub-key
*
* @return DateTime|null The creation date of this sub-key.
*/
public function getCreationDateTime()
{
return $this->_creationDate ? $this->_creationDate : null;
}
/**
* Gets the date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* zero. Warning: On 32-bit systems it returns invalid value for dates
* after 2038-01-19. Use getExpirationDateTime().
*
* @return integer the date this sub-key expires.
*/
public function getExpirationDate()
{
return $this->_expirationDate ? (int) $this->_expirationDate->format('U') : 0;
}
/**
* Gets the date-time (UTC) this sub-key expires
*
* @return integer the date this sub-key expires.
*/
public function getExpirationDateTime()
{
return $this->_expirationDate ? $this->_expirationDate : null;
}
/**
* Gets the fingerprint of this sub-key
*
* @return string the fingerprint of this sub-key.
*/
public function getFingerprint()
{
return $this->_fingerprint;
}
/**
* Gets the length of this sub-key in bits
*
* @return integer the length of this sub-key in bits.
*/
public function getLength()
{
return $this->_length;
}
/**
* Gets whether or not this sub-key can sign data
*
* @return boolean true if this sub-key can sign data and false if this
* sub-key can not sign data.
*/
public function canSign()
{
return ($this->_usage & self::USAGE_SIGN) != 0;
}
/**
* Gets whether or not this sub-key can encrypt data
*
* @return boolean true if this sub-key can encrypt data and false if this
* sub-key can not encrypt data.
*/
public function canEncrypt()
{
return ($this->_usage & self::USAGE_ENCRYPT) != 0;
}
/**
* Gets usage flags of this sub-key
*
* @return int Sum of usage flags
*/
public function usage()
{
return $this->_usage;
}
/**
* Gets whether or not the private key for this sub-key exists in the
* keyring
*
* @return boolean true the private key for this sub-key exists in the
* keyring and false if it does not.
*/
public function hasPrivate()
{
return $this->_hasPrivate;
}
/**
* Gets whether or not this sub-key is revoked
*
* @return boolean true if this sub-key is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
/**
* Sets the creation date of this sub-key
*
* The creation date is a Unix timestamp or DateTime object.
*
* @param integer|DateTime $creationDate the creation date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
if (empty($creationDate)) {
$this->_creationDate = null;
return $this;
}
if ($creationDate instanceof DateTime) {
$this->_creationDate = $creationDate;
} else {
$tz = new DateTimeZone('UTC');
$this->_creationDate = new DateTime("@$creationDate", $tz);
}
return $this;
}
/**
* Sets the expiration date of this sub-key
*
* The expiration date is a Unix timestamp. Specify zero if this sub-key
* does not expire.
*
* @param integer|DateTime $expirationDate the expiration date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
if (empty($expirationDate)) {
$this->_expirationDate = null;
return $this;
}
if ($expirationDate instanceof DateTime) {
$this->_expirationDate = $expirationDate;
} else {
$tz = new DateTimeZone('UTC');
$this->_expirationDate = new DateTime("@$expirationDate", $tz);
}
return $this;
}
/**
* Sets the id of this sub-key
*
* @param string $id the id of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
/**
* Sets the algorithm used by this sub-key
*
* @param integer $algorithm the algorithm used by this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setAlgorithm($algorithm)
{
$this->_algorithm = intval($algorithm);
return $this;
}
/**
* Sets the fingerprint of this sub-key
*
* @param string $fingerprint the fingerprint of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setFingerprint($fingerprint)
{
$this->_fingerprint = strval($fingerprint);
return $this;
}
/**
* Sets the length of this sub-key in bits
*
* @param integer $length the length of this sub-key in bits.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setLength($length)
{
$this->_length = intval($length);
return $this;
}
/**
* Sets whether or not this sub-key can sign data
*
* @param boolean $canSign true if this sub-key can sign data and false if
* it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanSign($canSign)
{
if ($canSign) {
$this->_usage |= self::USAGE_SIGN;
} else {
$this->_usage &= ~self::USAGE_SIGN;
}
return $this;
}
/**
* Sets whether or not this sub-key can encrypt data
*
* @param boolean $canEncrypt true if this sub-key can encrypt data and
* false if it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanEncrypt($canEncrypt)
{
if ($canEncrypt) {
$this->_usage |= self::USAGE_ENCRYPT;
} else {
$this->_usage &= ~self::USAGE_ENCRYPT;
}
return $this;
}
/**
* Sets usage flags of the sub-key
*
* @param integer $usage Usage flags
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setUsage($usage)
{
$this->_usage = (int) $usage;
return $this;
}
/**
* Sets whether of not the private key for this sub-key exists in the
* keyring
*
* @param boolean $hasPrivate true if the private key for this sub-key
* exists in the keyring and false if it does
* not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setHasPrivate($hasPrivate)
{
$this->_hasPrivate = ($hasPrivate) ? true : false;
return $this;
}
/**
* Sets whether or not this sub-key is revoked
*
* @param boolean $isRevoked whether or not this sub-key is revoked.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
/**
* Parses a sub-key object from a sub-key string
*
* See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for information
* on how the sub-key string is parsed.
*
* @param string $string the string containing the sub-key.
*
* @return Crypt_GPG_SubKey the sub-key object parsed from the string.
*/
public static function parse($string)
{
$tokens = explode(':', $string);
$subKey = new Crypt_GPG_SubKey();
$subKey->setId($tokens[4]);
$subKey->setLength($tokens[2]);
$subKey->setAlgorithm($tokens[3]);
$subKey->setCreationDate(self::_parseDate($tokens[5]));
$subKey->setExpirationDate(self::_parseDate($tokens[6]));
if ($tokens[1] == 'r') {
$subKey->setRevoked(true);
}
$usage = 0;
$usage_map = array(
'a' => self::USAGE_AUTHENTICATION,
'c' => self::USAGE_CERTIFY,
'e' => self::USAGE_ENCRYPT,
's' => self::USAGE_SIGN,
);
foreach ($usage_map as $key => $flag) {
if (strpos($tokens[11], $key) !== false) {
$usage |= $flag;
}
}
$subKey->setUsage($usage);
return $subKey;
}
/**
* Parses a date string as provided by GPG into a UNIX timestamp
*
* @param string $string the date string.
*
* @return DateTime|null the date corresponding to the provided date string.
*/
private static function _parseDate($string)
{
if (empty($string)) {
return null;
}
// all times are in UTC according to GPG documentation
$timeZone = new DateTimeZone('UTC');
if (strpos($string, 'T') === false) {
// interpret as UNIX timestamp
$string = '@' . $string;
}
return new DateTime($string, $timeZone);
}
}
+328
View File
@@ -0,0 +1,328 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a data class representing a GPG user id
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* A class for GPG user id information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. User id objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getUserIds()
*/
class Crypt_GPG_UserId
{
/**
* The name field of this user id
*
* @var string
*/
private $_name = '';
/**
* The comment field of this user id
*
* @var string
*/
private $_comment = '';
/**
* The email field of this user id
*
* @var string
*/
private $_email = '';
/**
* Whether or not this user id is revoked
*
* @var boolean
*/
private $_isRevoked = false;
/**
* Whether or not this user id is valid
*
* @var boolean
*/
private $_isValid = true;
/**
* Creates a new user id
*
* User ids can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string name</kbd> - the name field of the user id.
* - <kbd>string comment</kbd> - the comment field of the user id.
* - <kbd>string email</kbd> - the email field of the user id.
* - <kbd>boolean valid</kbd> - whether or not the user id is valid.
* - <kbd>boolean revoked</kbd> - whether or not the user id is revoked.
*
* @param Crypt_GPG_UserId|string|array|null $userId Either an existing user id object,
* which is copied; a user id string,
* which is parsed; or an array of
* initial values.
*/
public function __construct($userId = null)
{
// parse from string
if (is_string($userId)) {
$userId = self::parse($userId);
}
// copy from object
if ($userId instanceof Crypt_GPG_UserId) {
$this->_name = $userId->_name;
$this->_comment = $userId->_comment;
$this->_email = $userId->_email;
$this->_isRevoked = $userId->_isRevoked;
$this->_isValid = $userId->_isValid;
}
// initialize from array
if (is_array($userId)) {
if (array_key_exists('name', $userId)) {
$this->setName($userId['name']);
}
if (array_key_exists('comment', $userId)) {
$this->setComment($userId['comment']);
}
if (array_key_exists('email', $userId)) {
$this->setEmail($userId['email']);
}
if (array_key_exists('revoked', $userId)) {
$this->setRevoked($userId['revoked']);
}
if (array_key_exists('valid', $userId)) {
$this->setValid($userId['valid']);
}
}
}
/**
* Gets the name field of this user id
*
* @return string the name field of this user id.
*/
public function getName()
{
return $this->_name;
}
/**
* Gets the comments field of this user id
*
* @return string the comments field of this user id.
*/
public function getComment()
{
return $this->_comment;
}
/**
* Gets the email field of this user id
*
* @return string the email field of this user id.
*/
public function getEmail()
{
return $this->_email;
}
/**
* Gets whether or not this user id is revoked
*
* @return boolean true if this user id is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
/**
* Gets whether or not this user id is valid
*
* @return boolean true if this user id is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
/**
* Gets a string representation of this user id
*
* The string is formatted as:
* <b><kbd>name (comment) <email-address></kbd></b>.
*
* @return string a string representation of this user id.
*/
public function __toString()
{
$components = array();
if (mb_strlen($this->_name, '8bit') > 0) {
$components[] = $this->_name;
}
if (mb_strlen($this->_comment, '8bit') > 0) {
$components[] = '(' . $this->_comment . ')';
}
if (mb_strlen($this->_email, '8bit') > 0) {
$components[] = '<' . $this->_email. '>';
}
return implode(' ', $components);
}
/**
* Sets the name field of this user id
*
* @param string $name the name field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setName($name)
{
$this->_name = strval($name);
return $this;
}
/**
* Sets the comment field of this user id
*
* @param string $comment the comment field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setComment($comment)
{
$this->_comment = strval($comment);
return $this;
}
/**
* Sets the email field of this user id
*
* @param string $email the email field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setEmail($email)
{
$this->_email = strval($email);
return $this;
}
/**
* Sets whether or not this user id is revoked
*
* @param boolean $isRevoked whether or not this user id is revoked.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
/**
* Sets whether or not this user id is valid
*
* @param boolean $isValid whether or not this user id is valid.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
/**
* Parses a user id object from a user id string
*
* A user id string is of the form:
* <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i>
* and <i>email-address</i> fields being optional.
*
* @param string $string the user id string to parse.
*
* @return Crypt_GPG_UserId the user id object parsed from the string.
*/
public static function parse($string)
{
$userId = new Crypt_GPG_UserId();
$name = '';
$email = '';
$comment = '';
// get email address from end of string if it exists
$matches = array();
if (preg_match('/^(.*?)<([^>]+)>$/', $string, $matches) === 1) {
$string = trim($matches[1]);
$email = $matches[2];
}
// get comment from end of string if it exists
$matches = array();
if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) {
$string = $matches[1];
$comment = $matches[2];
}
// there can be an email without a name
if (!$email && preg_match('/^[\S]+@[\S]+$/', $string, $matches) === 1) {
$email = $string;
} else {
$name = $string;
}
$userId->setName($name);
$userId->setComment($comment);
$userId->setEmail($email);
return $userId;
}
}
+436
View File
@@ -0,0 +1,436 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This package provides an object oriented interface to GNU Privacy
* Guard (GPG). It requires the GPG executable to be on the system.
*
* Though GPG can support symmetric-key cryptography, this package is intended
* only to facilitate public-key cryptography.
*
* This file contains an abstract implementation of a user of the
* {@link Crypt_GPG_Engine} class.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
* @link http://www.gnupg.org/
*/
/**
* GPG key class
*/
require_once 'Crypt/GPG/Key.php';
/**
* GPG sub-key class
*/
require_once 'Crypt/GPG/SubKey.php';
/**
* GPG user id class
*/
require_once 'Crypt/GPG/UserId.php';
/**
* GPG process and I/O engine class
*/
require_once 'Crypt/GPG/Engine.php';
/**
* Base class for implementing a user of {@link Crypt_GPG_Engine}
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
abstract class Crypt_GPGAbstract
{
/**
* Error code returned when there is no error.
*/
const ERROR_NONE = 0;
/**
* Error code returned when an unknown or unhandled error occurs.
*/
const ERROR_UNKNOWN = 1;
/**
* Error code returned when a bad passphrase is used.
*/
const ERROR_BAD_PASSPHRASE = 2;
/**
* Error code returned when a required passphrase is missing.
*/
const ERROR_MISSING_PASSPHRASE = 3;
/**
* Error code returned when a key that is already in the keyring is
* imported.
*/
const ERROR_DUPLICATE_KEY = 4;
/**
* Error code returned the required data is missing for an operation.
*
* This could be missing key data, missing encrypted data or missing
* signature data.
*/
const ERROR_NO_DATA = 5;
/**
* Error code returned when an unsigned key is used.
*/
const ERROR_UNSIGNED_KEY = 6;
/**
* Error code returned when a key that is not self-signed is used.
*/
const ERROR_NOT_SELF_SIGNED = 7;
/**
* Error code returned when a public or private key that is not in the
* keyring is used.
*/
const ERROR_KEY_NOT_FOUND = 8;
/**
* Error code returned when an attempt to delete public key having a
* private key is made.
*/
const ERROR_DELETE_PRIVATE_KEY = 9;
/**
* Error code returned when one or more bad signatures are detected.
*/
const ERROR_BAD_SIGNATURE = 10;
/**
* Error code returned when there is a problem reading GnuPG data files.
*/
const ERROR_FILE_PERMISSIONS = 11;
/**
* Error code returned when a key could not be created.
*/
const ERROR_KEY_NOT_CREATED = 12;
/**
* Error code returned when bad key parameters are used during key
* generation.
*/
const ERROR_BAD_KEY_PARAMS = 13;
/**
* URI at which package bugs may be reported.
*/
const BUG_URI = 'http://pear.php.net/bugs/report.php?package=Crypt_GPG';
/**
* Engine used to control the GPG subprocess
*
* @var Crypt_GPG_Engine
*
* @see Crypt_GPGAbstract::setEngine()
*/
protected $engine = null;
/**
* Creates a new GPG object
*
* Available options are:
*
* - <kbd>string homedir</kbd> - the directory where the GPG keyring files are
* stored. If not specified, Crypt_GPG uses the default
* of <kbd>~/.gnupg</kbd>.
* - <kbd>string publicKeyring</kbd> - the file path of the public keyring.
* Use this if the public keyring is not in the homedir,
* or if the keyring is in a directory not writable
* by the process invoking GPG (like Apache). Then you
* can specify the path to the keyring with this option
* (/foo/bar/pubring.gpg), and specify a writable directory
* (like /tmp) using the <i>homedir</i> option.
* - <kbd>string privateKeyring</kbd> - the file path of the private keyring.
* Use this if the private keyring is not in the homedir,
* or if the keyring is in a directory not writable
* by the process invoking GPG (like Apache). Then
* you can specify the path to the keyring with this option
* (/foo/bar/secring.gpg), and specify a writable directory
* (like /tmp) using the <i>homedir</i> option.
* - <kbd>string trustDb</kbd> - the file path of the web-of-trust database.
* Use this if the trust database is not in the homedir, or
* if the database is in a directory not writable
* by the process invoking GPG (like Apache). Then you can
* specify the path to the trust database with this option
* (/foo/bar/trustdb.gpg), and specify a writable directory
* (like /tmp) using the <i>homedir</i> option.
* - <kbd>string binary</kbd> - the location of the GPG binary.
* If not specified, the driver attempts to auto-detect
* the GPG binary location using a list of known default
* locations for the current operating system. The option
* <kbd>gpgBinary</kbd> is a deprecated alias.
* - <kbd>string agent</kbd> - the location of the GnuPG agent binary.
* The gpg-agent is only used for GnuPG 2.x. If not
* specified, the engine attempts to auto-detect
* the gpg-agent binary location using a list of
* know default locations for the current operating system.
* - <kbd>string|false gpgconf</kbd> - the location of the GnuPG conf binary.
* The gpgconf is only used for GnuPG >= 2.1. If not
* specified, the engine attempts to auto-detect
* the location using a list of know default locations.
* When set to FALSE `gpgconf --kill` will not be executed
* via destructor.
* - <kbd>string digest-algo</kbd> - Sets the message digest algorithm.
* - <kbd>string cipher-algo</kbd> - Sets the symmetric cipher.
* - <kbd>string compress-algo</kbd> - Sets the compression algorithm.
* - <kbd>boolean strict</kbd> - In strict mode clock problems on subkeys
* and signatures are not ignored (--ignore-time-conflict
* and --ignore-valid-from options).
* - <kbd>mixed debug</kbd> - whether or not to use debug mode.
* When debug mode is on, all communication to and from
* the GPG subprocess is logged. This can be useful to
* diagnose errors when using Crypt_GPG.
* - <kbd>array options</kbd> - additional per-command options to the GPG
* command. Key of the array is a command (e.g.
* gen-key, import, sign, encrypt, list-keys).
* Value is a string containing command line arguments to be
* added to the related command. For example:
* array('sign' => '--emit-version').
*
* @param array $options optional. An array of options used to create the
* GPG object. All options are optional and are
* represented as key-value pairs.
*
* @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
* and cannot be created. This can happen if <kbd>homedir</kbd> is
* not specified, Crypt_GPG is run as the web user, and the web
* user has no home directory. This exception is also thrown if any
* of the options <kbd>publicKeyring</kbd>,
* <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
* specified but the files do not exist or are are not readable.
* This can happen if the user running the Crypt_GPG process (for
* example, the Apache user) does not have permission to read the
* files.
*
* @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
* if no <kbd>binary</kbd> is provided and no suitable binary could
* be found.
*
* @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
* if no <kbd>agent</kbd> is provided and no suitable gpg-agent
* could be found.
*/
public function __construct(array $options = array())
{
$this->setEngine(new Crypt_GPG_Engine($options));
}
/**
* Sets the I/O engine to use for GnuPG operations
*
* Normally this method does not need to be used. It provides a means for
* dependency injection.
*
* @param Crypt_GPG_Engine $engine the engine to use.
*
* @return Crypt_GPGAbstract the current object, for fluent interface.
*/
public function setEngine(Crypt_GPG_Engine $engine)
{
$this->engine = $engine;
return $this;
}
/**
* Sets per-command additional arguments
*
* @param array $options Additional per-command options for GPG command.
* Note: This will unset options set previously.
* Key of the array is a command (e.g.
* gen-key, import, sign, encrypt, list-keys).
* Value is a string containing command line arguments to be
* added to the related command. For example:
* array('sign' => '--emit-version').
*
* @return Crypt_GPGAbstract the current object, for fluent interface.
*/
public function setEngineOptions(array $options)
{
$this->engine->setOptions($options);
return $this;
}
/**
* Returns version of the engine (GnuPG) used for operation.
*
* @return string GnuPG version.
*
* @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
* Use the <kbd>debug</kbd> option and file a bug report if these
* exceptions occur.
*/
public function getVersion()
{
return $this->engine->getVersion();
}
/**
* Gets the available keys in the keyring
*
* Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See
* the first section of <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
* description of how the GPG command output is parsed.
*
* @param string $keyId optional. Only keys with that match the specified
* pattern are returned. The pattern may be part of
* a user id, a key id or a key fingerprint. If not
* specified, all keys are returned.
*
* @return array an array of {@link Crypt_GPG_Key} objects. If no keys
* match the specified <kbd>$keyId</kbd> an empty array is
* returned.
*
* @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
* Use the <kbd>debug</kbd> option and file a bug report if these
* exceptions occur.
*
* @see Crypt_GPG_Key
*/
protected function _getKeys($keyId = '')
{
// get private key fingerprints
if ($keyId == '') {
$operation = '--list-secret-keys';
} else {
$operation = '--utf8-strings --list-secret-keys -- ' . escapeshellarg($keyId);
}
// According to The file 'doc/DETAILS' in the GnuPG distribution, using
// double '--with-fingerprint' also prints the fingerprint for subkeys.
$arguments = array(
'--with-colons',
'--with-fingerprint',
'--with-fingerprint',
'--fixed-list-mode'
);
$output = '';
$this->engine->reset();
$this->engine->setOutput($output);
$this->engine->setOperation($operation, $arguments);
$this->engine->run();
$privateKeyFingerprints = array();
foreach (explode(PHP_EOL, $output) as $line) {
$lineExp = explode(':', $line);
if ($lineExp[0] == 'fpr') {
$privateKeyFingerprints[] = $lineExp[9];
}
}
// get public keys
if ($keyId == '') {
$operation = '--list-public-keys';
} else {
$operation = '--utf8-strings --list-public-keys -- ' . escapeshellarg($keyId);
}
$output = '';
$this->engine->reset();
$this->engine->setOutput($output);
$this->engine->setOperation($operation, $arguments);
$this->engine->run();
$keys = array();
$key = null; // current key
$subKey = null; // current sub-key
foreach (explode(PHP_EOL, $output) as $line) {
$lineExp = explode(':', $line);
if ($lineExp[0] == 'pub') {
// new primary key means last key should be added to the array
if ($key !== null) {
$keys[] = $key;
}
$key = new Crypt_GPG_Key();
$subKey = Crypt_GPG_SubKey::parse($line);
$key->addSubKey($subKey);
} elseif ($lineExp[0] == 'sub') {
$subKey = Crypt_GPG_SubKey::parse($line);
$key->addSubKey($subKey);
} elseif ($lineExp[0] == 'fpr') {
$fingerprint = $lineExp[9];
// set current sub-key fingerprint
$subKey->setFingerprint($fingerprint);
// if private key exists, set has private to true
if (in_array($fingerprint, $privateKeyFingerprints)) {
$subKey->setHasPrivate(true);
}
} elseif ($lineExp[0] == 'uid') {
$string = stripcslashes($lineExp[9]); // as per documentation
$userId = new Crypt_GPG_UserId($string);
if ($lineExp[1] == 'r') {
$userId->setRevoked(true);
}
$key->addUserId($userId);
}
}
// add last key
if ($key !== null) {
$keys[] = $key;
}
return $keys;
}
}
+502
View File
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
+48
View File
@@ -0,0 +1,48 @@
# Crypt_GPG #
Crypt_GPG is a PHP package to interact with the [GNU Privacy Guard
(GnuPG)](https://www.gnupg.org/). GnuPG is a free and open-source
implementation of the [OpenPGP](https://www.ietf.org/rfc/rfc4880.txt)
protocol, providing key management, data encryption and data signing.
Crypt_GPG provides an object-oriented API for performing OpenPGP
actions using GnuPG.
## Documentation ##
### Quick Example
```php
<?php
require_once 'Crypt/GPG.php';
$gpg = new Crypt_GPG();
$gpg->addEncryptKey('test@example.com');
$data = $gpg->encrypt('my secret data');
?>
```
### Further Documentation ###
* [High-Level Documentation](https://pear.php.net/manual/en/package.encryption.crypt-gpg.intro.php)
* [Detailed API Documentation](https://pear.php.net/package/Crypt_GPG/docs/latest/)
## Bugs and Issues ##
Please report all new issues via the [PEAR bug tracker](https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Crypt_GPG).
Please submit pull requests for your bug reports!
## Testing ##
To test, run either
`$ phpunit tests/`
or
`$ pear run-tests -r`
## Building ##
To build, simply
`$ pear package`
## Installing ##
To install from scratch
`$ pear install package.xml`
To upgrade
`$ pear upgrade -f package.xml`
+55
View File
@@ -0,0 +1,55 @@
{
"name": "pear/crypt_gpg",
"description": "Provides an object oriented interface to the GNU Privacy Guard (GnuPG). It requires the GnuPG executable to be on the system.",
"type": "library",
"keywords": [
"gpg",
"gnupg",
"encryption",
"pgp"
],
"homepage": "https://github.com/pear/Crypt_GPG",
"license": "LGPL-2.1",
"authors": [
{
"name": "Michael Gauthier",
"email": "mike@silverorange.com"
},
{
"name": "Nathan Fredrickson",
"email": "nathan@silverorange.com"
},
{
"name": "Aleksander Machniak",
"email": "alec@alec.pl"
}
],
"require": {
"php": ">=5.4.8",
"ext-mbstring": "*",
"pear/console_commandline": "*",
"pear/pear_exception": "*"
},
"suggest": {
"ext-posix": "May require the posix PHP extension"
},
"bin": [
"scripts/crypt-gpg-pinentry"
],
"autoload": {
"classmap": ["Crypt/"]
},
"include-path": [
"./"
],
"support": {
"issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Crypt_GPG",
"source": "https://github.com/pear/Crypt_GPG"
},
"require-dev": {
"phpunit/phpunit": "^9"
},
"archive": {
"exclude": ["tools", "package.php"]
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<command>
<description>Utility that emulates GnuPG 1.x passphrase handling over pipe-based IPC for GnuPG 2.x.</description>
<version>@package-version@</version>
<option name="log">
<short_name>-l</short_name>
<long_name>--log</long_name>
<description>Optional location to log pinentry activity.</description>
<action>StoreString</action>
</option>
<option name="verbose">
<short_name>-v</short_name>
<long_name>--verbose</long_name>
<description>Sets verbosity level. Use multiples for more detail (e.g. "-vv").</description>
<action>Counter</action>
<default>0</default>
</option>
</command>
@@ -0,0 +1,33 @@
#! /usr/bin/env php
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
// Check if we're running directly from git repo or if we're running
// from a PEAR or Composer packaged version.
$ds = DIRECTORY_SEPARATOR;
$root = __DIR__ . $ds . '..' ;
$paths = array(
'@php-dir@', // PEAR or Composer
$root, // Git (or Composer with wrong @php-dir@)
$root . $ds . '..' . $ds . 'Console_CommandLine', // Composer
$root . $ds . '..' . $ds . 'console_commandline', // Composer
// and composer-installed PEAR_Exception for Console_CommandLine (#21074)
$root . $ds . '..' . $ds . '..' . $ds . 'pear' . $ds . 'pear_exception',
);
foreach ($paths as $idx => $path) {
if (!is_dir($path)) {
unset($paths[$idx]);
}
}
// We depend on Console_CommandLine, so we append also the default include path
set_include_path(implode(PATH_SEPARATOR, $paths) . PATH_SEPARATOR . get_include_path());
require_once 'Crypt/GPG/PinEntry.php';
$pinentry = new Crypt_GPG_PinEntry();
$pinentry->__invoke();
?>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
This package is http://pear.php.net/package/Mail_Mime and has been migrated from http://svn.php.net/repository/pear/packages/Mail_Mime
Please report all new issues via the PEAR bug tracker.
If this package is marked as unmaintained and you have fixes, please submit your pull requests and start discussion on the pear-qa mailing list.
To test, run either
$ phpunit tests/
or
$ pear run-tests -r
To build, simply
$ pear package
To install from scratch
$ pear install package.xml
To upgrade
$ pear upgrade -f package.xml
+35
View File
@@ -0,0 +1,35 @@
{
"authors": [
{
"email": "cipri@php.net",
"name": "Cipriano Groenendal",
"role": "Lead"
},
{
"email": "alec@php.net",
"name": "Aleksander Machniak",
"role": "Lead"
}
],
"autoload": {
"psr-0": {
"Mail": "./"
}
},
"description": "Mail_Mime provides classes to create MIME messages",
"homepage": "http://pear.php.net/package/Mail_Mime",
"include-path": [
"./"
],
"license": "BSD-3-Clause",
"name": "pear/mail_mime",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Mail_Mime",
"source": "https://github.com/pear/Mail_Mime"
},
"type": "library",
"require": {
"php": ">=5.2.0",
"pear/pear-core-minimal": "*"
}
}
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+675
View File
@@ -0,0 +1,675 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Filter interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2/Util.php';
require_once 'Net/LDAP2/Entry.php';
/**
* Object representation of a part of a LDAP filter.
*
* This Class is not completely compatible to the PERL interface!
*
* The purpose of this class is, that users can easily build LDAP filters
* without having to worry about right escaping etc.
* A Filter is built using several independent filter objects
* which are combined afterwards. This object works in two
* modes, depending how the object is created.
* If the object is created using the {@link create()} method, then this is a leaf-object.
* If the object is created using the {@link combine()} method, then this is a container object.
*
* LDAP filters are defined in RFC-2254 and can be found under
* {@link http://www.ietf.org/rfc/rfc2254.txt}
*
* Here a quick copy&paste example:
* <code>
* $filter0 = Net_LDAP2_Filter::create('stars', 'equals', '***');
* $filter_not0 = Net_LDAP2_Filter::combine('not', $filter0);
*
* $filter1 = Net_LDAP2_Filter::create('gn', 'begins', 'bar');
* $filter2 = Net_LDAP2_Filter::create('gn', 'ends', 'baz');
* $filter_comp = Net_LDAP2_Filter::combine('or',array($filter_not0, $filter1, $filter2));
*
* echo $filter_comp->asString();
* // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz))
* // The stars in $filter0 are treaten as real stars unless you disable escaping.
* </code>
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP2/
*/
class Net_LDAP2_Filter extends PEAR
{
/**
* Storage for combination of filters
*
* This variable holds a array of filter objects
* that should be combined by this filter object.
*
* @access protected
* @var array
*/
protected $_subfilters = array();
/**
* Match of this filter
*
* If this is a leaf filter, then a matching rule is stored,
* if it is a container, then it is a logical operator
*
* @access protected
* @var string
*/
protected $_match;
/**
* Single filter
*
* If we operate in leaf filter mode,
* then the constructing method stores
* the filter representation here
*
* @acces private
* @var string
*/
protected $_filter;
/**
* Create a new Net_LDAP2_Filter object and parse $filter.
*
* This is for PERL Net::LDAP interface.
* Construction of Net_LDAP2_Filter objects should happen through either
* {@link create()} or {@link combine()} which give you more control.
* However, you may use the perl iterface if you already have generated filters.
*
* @param string $filter LDAP filter string
*
* @see parse()
*/
public function __construct($filter = false)
{
// The optional parameter must remain here, because otherwise create() crashes
if (false !== $filter) {
$filter_o = self::parse($filter);
if (PEAR::isError($filter_o)) {
$this->_filter = $filter_o; // assign error, so asString() can report it
} else {
$this->_filter = $filter_o->asString();
}
}
}
/**
* Constructor of a new part of a LDAP filter.
*
* The following matching rules exists:
* - equals: One of the attributes values is exactly $value
* Please note that case sensitiviness is depends on the
* attributes syntax configured in the server.
* - begins: One of the attributes values must begin with $value
* - ends: One of the attributes values must end with $value
* - contains: One of the attributes values must contain $value
* - present | any: The attribute can contain any value but must be existent
* - greater: The attributes value is greater than $value
* - less: The attributes value is less than $value
* - greaterOrEqual: The attributes value is greater or equal than $value
* - lessOrEqual: The attributes value is less or equal than $value
* - approx: One of the attributes values is similar to $value
*
* Negation ("not") can be done by prepending the above operators with the
* "not" or "!" keyword, see example below.
*
* If $escape is set to true (default) then $value will be escaped
* properly. If it is set to false then $value will be treaten as raw filter value string.
* You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
*
* Examples:
* <code>
* // This will find entries that contain an attribute "sn" that ends with "foobar":
* $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
*
* // This will find entries that contain an attribute "sn" that has any value set:
* $filter = Net_LDAP2_Filter::create('sn', 'any');
*
* // This will build a negated equals filter:
* $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
* </code>
*
* @param string $attr_name Name of the attribute the filter should apply to
* @param string $match Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any)
* @param string $value (optional) if given, then this is used as a filter
* @param boolean $escape Should $value be escaped? (default: yes, see {@link Net_LDAP2_Util::escape_filter_value()} for detailed information)
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
*/
public static function create($attr_name, $match, $value = '', $escape = true)
{
$leaf_filter = new Net_LDAP2_Filter();
if ($escape) {
$array = Net_LDAP2_Util::escape_filter_value(array($value));
$value = $array[0];
}
$match = strtolower($match);
// detect negation
$neg_matches = array();
$negate_filter = false;
if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
$negate_filter = true;
$match = $neg_matches[1];
}
// build basic filter
switch ($match) {
case 'equals':
case '=':
case '==':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
break;
case 'begins':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)';
break;
case 'ends':
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')';
break;
case 'contains':
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
break;
case 'greater':
case '>':
$leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
break;
case 'less':
case '<':
$leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
break;
case 'greaterorequal':
case '>=':
$leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
break;
case 'lessorequal':
case '<=':
$leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
break;
case 'approx':
case '~=':
$leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
break;
case 'any':
case 'present': // alias that may improve user code readability
$leaf_filter->_filter = '(' . $attr_name . '=*)';
break;
default:
return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
}
// negate if requested
if ($negate_filter) {
$leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
}
return $leaf_filter;
}
/**
* Combine two or more filter objects using a logical operator
*
* This static method combines two or more filter objects and returns one single
* filter object that contains all the others.
* Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
* If the array contains filter strings instead of filter objects, we will try to parse them.
*
* @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
* @static
*/
public static function &combine($log_op, $filters)
{
if (PEAR::isError($filters)) {
return $filters;
}
// substitude named operators to logical operators
if ($log_op == 'and') $log_op = '&';
if ($log_op == 'or') $log_op = '|';
if ($log_op == 'not') $log_op = '!';
// tests for sane operation
if ($log_op == '!') {
// Not-combination, here we only accept one filter object or filter string
if ($filters instanceof Net_LDAP2_Filter) {
$filters = array($filters); // force array
} elseif (is_string($filters)) {
$filter_o = self::parse($filters);
if (PEAR::isError($filter_o)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: '.$filter_o->getMessage());
return $err;
} else {
$filters = array($filter_o);
}
} elseif (is_array($filters)) {
if (count($filters) != 1) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
return $err;
} elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
}
} elseif ($log_op == '&' || $log_op == '|') {
if (!is_array($filters) || count($filters) < 2) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP2_Filter objects!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: logical operator is not known!');
return $err;
}
$combined_filter = new Net_LDAP2_Filter();
foreach ($filters as $key => $testfilter) { // check for errors
if (PEAR::isError($testfilter)) {
return $testfilter;
} elseif (is_string($testfilter)) {
// string found, try to parse into an filter object
$filter_o = self::parse($testfilter);
if (PEAR::isError($filter_o)) {
return $filter_o;
} else {
$filters[$key] = $filter_o;
}
} elseif (!$testfilter instanceof Net_LDAP2_Filter) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: invalid object passed in array $filters!');
return $err;
}
}
$combined_filter->_subfilters = $filters;
$combined_filter->_match = $log_op;
return $combined_filter;
}
/**
* Parse FILTER into a Net_LDAP2_Filter object
*
* This parses an filter string into Net_LDAP2_Filter objects.
*
* @param string $FILTER The filter string
*
* @access static
* @return Net_LDAP2_Filter|Net_LDAP2_Error
* @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments)
*/
public static function parse($FILTER)
{
if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
// Check for right bracket syntax: count of unescaped opening
// brackets must match count of unescaped closing brackets.
// At this stage we may have:
// 1. one filter component with already removed outer brackets
// 2. one or more subfilter components
$c_openbracks = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
$c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
if ($c_openbracks != $c_closebracks) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - opening brackets do not match close brackets!");
}
if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
// Subfilter processing: pass subfilters to parse() and combine
// the objects using the logical operator detected
// we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
// Each subfilter could be an arbitary complex subfilter.
// extract logical operator and filter arguments
$log_op = substr($matches[1], 0, 1);
$remaining_component = substr($matches[1], 1);
// split $remaining_component into individual subfilters
// we cannot use split() for this, because we do not know the
// complexiness of the subfilter. Thus, we look trough the filter
// string and just recognize ending filters at the first level.
// We record the index number of the char and use that information
// later to split the string.
$sub_index_pos = array();
$prev_char = ''; // previous character looked at
$level = 0; // denotes the current bracket level we are,
// >1 is too deep, 1 is ok, 0 is outside any
// subcomponent
for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
$cur_char = substr($remaining_component, $curpos, 1);
// rise/lower bracket level
if ($cur_char == '(' && $prev_char != '\\') {
$level++;
} elseif ($cur_char == ')' && $prev_char != '\\') {
$level--;
}
if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
array_push($sub_index_pos, $curpos); // mark the position for splitting
}
$prev_char = $cur_char;
}
// now perform the splits. To get also the last part, we
// need to add the "END" index to the split array
array_push($sub_index_pos, strlen($remaining_component));
$subfilters = array();
$oldpos = 0;
foreach ($sub_index_pos as $s_pos) {
$str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
array_push($subfilters, $str_part);
$oldpos = $s_pos;
}
// some error checking...
if (count($subfilters) == 1) {
// only one subfilter found
} elseif (count($subfilters) > 1) {
// several subfilters found
if ($log_op == "!") {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - NOT operator detected but several arguments given!");
}
} else {
// this should not happen unless the user specified a wrong filter
return PEAR::raiseError("Filter parsing error: invalid filter syntax - got operator '$log_op' but no argument!");
}
// Now parse the subfilters into objects and combine them using the operator
$subfilters_o = array();
foreach ($subfilters as $s_s) {
$o = self::parse($s_s);
if (PEAR::isError($o)) {
return $o;
} else {
array_push($subfilters_o, self::parse($s_s));
}
}
$filter_o = self::combine($log_op, $subfilters_o);
return $filter_o;
} else {
// This is one leaf filter component, do some syntax checks, then escape and build filter_o
// $matches[1] should be now something like "foo=bar"
// detect multiple leaf components
// [TODO] Maybe this will make problems with filters containing brackets inside the value
if (stristr($matches[1], ')(')) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
} else {
$filter_parts = Net_LDAP2_Util::split_attribute_string($matches[1], true, true);
if (count($filter_parts) != 3) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
} else {
$filter_o = new Net_LDAP2_Filter();
// [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special?
// I think, those prevent escaping! We need to check against PERL Net::LDAP!
// $value_arr = Net_LDAP2_Util::escape_filter_value(array($filter_parts[2]));
// $value = $value_arr[0];
$value = $filter_parts[2];
$filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')';
return $filter_o;
}
}
}
} else {
// ERROR: Filter components must be enclosed in round brackets
return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets");
}
}
/**
* Get the string representation of this filter
*
* This method runs through all filter objects and creates
* the string representation of the filter. If this
* filter object is a leaf filter, then it will return
* the string representation of this filter.
*
* @return string|Net_LDAP2_Error
*/
public function asString()
{
if ($this->isLeaf()) {
$return = $this->_filter;
} else {
$return = '';
foreach ($this->_subfilters as $filter) {
$return = $return.$filter->asString();
}
$return = '(' . $this->_match . $return . ')';
}
return $return;
}
/**
* Alias for perl interface as_string()
*
* @see asString()
* @return string|Net_LDAP2_Error
*/
public function as_string()
{
return $this->asString();
}
/**
* Print the text representation of the filter to FH, or the currently selected output handle if FH is not given
*
* This method is only for compatibility to the perl interface.
* However, the original method was called "print" but due to PHP language restrictions,
* we can't have a print() method.
*
* @param resource $FH (optional) A filehandle resource
*
* @return true|Net_LDAP2_Error
*/
public function printMe($FH = false)
{
if (!is_resource($FH)) {
if (PEAR::isError($FH)) {
return $FH;
}
$filter_str = $this->asString();
if (PEAR::isError($filter_str)) {
return $filter_str;
} else {
print($filter_str);
}
} else {
$filter_str = $this->asString();
if (PEAR::isError($filter_str)) {
return $filter_str;
} else {
$res = @fwrite($FH, $this->asString());
if ($res == false) {
return PEAR::raiseError("Unable to write filter string to filehandle \$FH!");
}
}
}
return true;
}
/**
* This can be used to escape a string to provide a valid LDAP-Filter.
*
* LDAP will only recognise certain characters as the
* character istself if they are properly escaped. This is
* what this method does.
* The method can be called statically, so you can use it outside
* for your own purposes (eg for escaping only parts of strings)
*
* In fact, this is just a shorthand to {@link Net_LDAP2_Util::escape_filter_value()}.
* For upward compatibiliy reasons you are strongly encouraged to use the escape
* methods provided by the Net_LDAP2_Util class.
*
* @param string $value Any string who should be escaped
*
* @static
* @return string The string $string, but escaped
* @deprecated Do not use this method anymore, instead use Net_LDAP2_Util::escape_filter_value() directly
*/
public static function escape($value)
{
$return = Net_LDAP2_Util::escape_filter_value(array($value));
return $return[0];
}
/**
* Is this a container or a leaf filter object?
*
* @access protected
* @return boolean
*/
protected function isLeaf()
{
if (count($this->_subfilters) > 0) {
return false; // Container!
} else {
return true; // Leaf!
}
}
/**
* Filter entries using this filter or see if a filter matches
*
* @todo Currently slow and naive implementation with preg_match, could be optimized (esp. begins, ends filters etc)
* @todo Currently only "="-based matches (equals, begins, ends, contains, any) implemented; Implement all the stuff!
* @todo Implement expert code with schema checks in case $entry is connected to a directory
* @param array|Net_LDAP2_Entry The entry (or array with entries) to check
* @param array If given, the array will be appended with entries who matched the filter. Return value is true if any entry matched.
* @return int|Net_LDAP2_Error Returns the number of matched entries or error
*/
function matches(&$entries, &$results=array()) {
$numOfMatches = 0;
if (!is_array($entries)) {
$all_entries = array(&$entries);
} else {
$all_entries = &$entries;
}
foreach ($all_entries as $entry) {
// look at the current entry and see if filter matches
$entry_matched = false;
// if this is not a single component, do calculate all subfilters,
// then assert the partial results with the given combination modifier
if (!$this->isLeaf()) {
// get partial results from subfilters
$partial_results = array();
foreach ($this->_subfilters as $filter) {
$partial_results[] = $filter->matches($entry);
}
// evaluate partial results using this filters combination rule
switch ($this->_match) {
case '!':
// result is the neagtive result of the assertion
$entry_matched = !$partial_results[0];
break;
case '&':
// all partial results have to be boolean-true
$entry_matched = !in_array(false, $partial_results);
break;
case '|':
// at least one partial result has to be true
$entry_matched = in_array(true, $partial_results);
break;
}
} else {
// Leaf filter: assert given entry
// [TODO]: Could be optimized to avoid preg_match especially with "ends", "begins" etc
// Translate the LDAP-match to some preg_match expression and evaluate it
list($attribute, $match, $assertValue) = $this->getComponents();
switch ($match) {
case '=':
$regexp = '/^'.str_replace('*', '.*', $assertValue).'$/i'; // not case sensitive unless specified by schema
$entry_matched = $entry->pregMatch($regexp, $attribute);
break;
// -------------------------------------
// [TODO]: implement <, >, <=, >= and =~
// -------------------------------------
default:
$err = PEAR::raiseError("Net_LDAP2_Filter match error: unsupported match rule '$match'!");
return $err;
}
}
// process filter matching result
if ($entry_matched) {
$numOfMatches++;
$results[] = $entry;
}
}
return $numOfMatches;
}
/**
* Retrieve this leaf-filters attribute, match and value component.
*
* For leaf filters, this returns array(attr, match, value).
* Match is be the logical operator, not the text representation,
* eg "=" instead of "equals". Note that some operators are really
* a combination of operator+value with wildcard, like
* "begins": That will return "=" with the value "value*"!
*
* For non-leaf filters this will drop an error.
*
* @todo $this->_match is not always available and thus not usable here; it would be great if it would set in the factory methods and constructor.
* @return array|Net_LDAP2_Error
*/
function getComponents() {
if ($this->isLeaf()) {
$raw_filter = preg_replace('/^\(|\)$/', '', $this->_filter);
$parts = Net_LDAP2_Util::split_attribute_string($raw_filter, true, true);
if (count($parts) != 3) {
return PEAR::raiseError("Net_LDAP2_Filter getComponents() error: invalid filter syntax - unknown matching rule used");
} else {
return $parts;
}
} else {
return PEAR::raiseError('Net_LDAP2_Filter getComponents() call is invalid for non-leaf filters!');
}
}
}
?>
+925
View File
@@ -0,0 +1,925 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_LDIF interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2.php';
require_once 'Net/LDAP2/Entry.php';
require_once 'Net/LDAP2/Util.php';
/**
* LDIF capabilitys for Net_LDAP2, closely taken from PERLs Net::LDAP
*
* It provides a means to convert between Net_LDAP2_Entry objects and LDAP entries
* represented in LDIF format files. Reading and writing are supported and may
* manipulate single entries or lists of entries.
*
* Usage example:
* <code>
* // Read and parse an ldif-file into Net_LDAP2_Entry objects
* // and print out the DNs. Store the entries for later use.
* require 'Net/LDAP2/LDIF.php';
* $options = array(
* 'onerror' => 'die'
* );
* $entries = array();
* $ldif = new Net_LDAP2_LDIF('test.ldif', 'r', $options);
* do {
* $entry = $ldif->read_entry();
* $dn = $entry->dn();
* echo " done building entry: $dn\n";
* array_push($entries, $entry);
* } while (!$ldif->eof());
* $ldif->done();
*
*
* // write those entries to another file
* $ldif = new Net_LDAP2_LDIF('test.out.ldif', 'w', $options);
* $ldif->write_entry($entries);
* $ldif->done();
* </code>
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP22/
* @see http://www.ietf.org/rfc/rfc2849.txt
* @todo Error handling should be PEARified
* @todo LDAPv3 controls are not implemented yet
*/
class Net_LDAP2_LDIF extends PEAR
{
/**
* Options
*
* @access protected
* @var array
*/
protected $_options = array('encode' => 'base64',
'onerror' => null,
'change' => 0,
'lowercase' => 0,
'sort' => 0,
'version' => null,
'wrap' => 78,
'raw' => ''
);
/**
* Errorcache
*
* @access protected
* @var array
*/
protected $_error = array('error' => null,
'line' => 0
);
/**
* Filehandle for read/write
*
* @access protected
* @var array
*/
protected $_FH = null;
/**
* Says, if we opened the filehandle ourselves
*
* @access protected
* @var array
*/
protected $_FH_opened = false;
/**
* Linecounter for input file handle
*
* @access protected
* @var array
*/
protected $_input_line = 0;
/**
* counter for processed entries
*
* @access protected
* @var int
*/
protected $_entrynum = 0;
/**
* Mode we are working in
*
* Either 'r', 'a' or 'w'
*
* @access protected
* @var string
*/
protected $_mode = false;
/**
* Tells, if the LDIF version string was already written
*
* @access protected
* @var boolean
*/
protected $_version_written = false;
/**
* Cache for lines that have build the current entry
*
* @access protected
* @var boolean
*/
protected $_lines_cur = array();
/**
* Cache for lines that will build the next entry
*
* @access protected
* @var boolean
*/
protected $_lines_next = array();
/**
* Open LDIF file for reading or for writing
*
* new (FILE):
* Open the file read-only. FILE may be the name of a file
* or an already open filehandle.
* If the file doesn't exist, it will be created if in write mode.
*
* new (FILE, MODE, OPTIONS):
* Open the file with the given MODE (see PHPs fopen()), eg "w" or "a".
* FILE may be the name of a file or an already open filehandle.
* PERLs Net_LDAP2 "FILE|" mode does not work curently.
*
* OPTIONS is an associative array and may contain:
* encode => 'none' | 'canonical' | 'base64'
* Some DN values in LDIF cannot be written verbatim and have to be encoded in some way:
* 'none' No encoding.
* 'canonical' See "canonical_dn()" in Net::LDAP::Util.
* 'base64' Use base64. (default, this differs from the Perl interface.
* The perl default is "none"!)
*
* onerror => 'die' | 'warn' | NULL
* Specify what happens when an error is detected.
* 'die' Net_LDAP2_LDIF will croak with an appropriate message.
* 'warn' Net_LDAP2_LDIF will warn (echo) with an appropriate message.
* NULL Net_LDAP2_LDIF will not warn (default), use error().
*
* change => 1
* Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP
* operations acting on the entries to the file instead of the entries contents.
* This writes the changes usually carried out by an update() to the LDIF file.
*
* lowercase => 1
* Convert attribute names to lowercase when writing.
*
* sort => 1
* Sort attribute names when writing entries according to the rule:
* objectclass first then all other attributes alphabetically sorted by attribute name
*
* version => '1'
* Set the LDIF version to write to the resulting LDIF file.
* According to RFC 2849 currently the only legal value for this option is 1.
* When this option is set Net_LDAP2_LDIF tries to adhere more strictly to
* the LDIF specification in RFC2489 in a few places.
* The default is NULL meaning no version information is written to the LDIF file.
*
* wrap => 78
* Number of columns where output line wrapping shall occur.
* Default is 78. Setting it to 40 or lower inhibits wrapping.
*
* raw => REGEX
* Use REGEX to denote the names of attributes that are to be
* considered binary in search results if writing entries.
* Example: raw => "/(?i:^jpegPhoto|;binary)/i"
*
* @param string|ressource $file Filename or filehandle
* @param string $mode Mode to open filename
* @param array $options Options like described above
*/
public function __construct($file, $mode = 'r', $options = array())
{
parent::__construct('Net_LDAP2_Error'); // default error class
// First, parse options
// todo: maybe implement further checks on possible values
foreach ($options as $option => $value) {
if (!array_key_exists($option, $this->_options)) {
$this->dropError('Net_LDAP2_LDIF error: option '.$option.' not known!');
return;
} else {
$this->_options[$option] = strtolower($value);
}
}
// setup LDIF class
$this->version($this->_options['version']);
// setup file mode
if (!preg_match('/^[rwa]\+?$/', $mode)) {
$this->dropError('Net_LDAP2_LDIF error: file mode '.$mode.' not supported!');
} else {
$this->_mode = $mode;
// setup filehandle
if (is_resource($file)) {
// TODO: checks on mode possible?
$this->_FH =& $file;
} else {
$imode = substr($this->_mode, 0, 1);
if ($imode == 'r') {
if (!file_exists($file)) {
$this->dropError('Unable to open '.$file.' for read: file not found');
$this->_mode = false;
}
if (!is_readable($file)) {
$this->dropError('Unable to open '.$file.' for read: permission denied');
$this->_mode = false;
}
}
if (($imode == 'w' || $imode == 'a')) {
if (file_exists($file)) {
if (!is_writable($file)) {
$this->dropError('Unable to open '.$file.' for write: permission denied');
$this->_mode = false;
}
} else {
if (!@touch($file)) {
$this->dropError('Unable to create '.$file.' for write: permission denied');
$this->_mode = false;
}
}
}
if ($this->_mode) {
$this->_FH = @fopen($file, $this->_mode);
if (false === $this->_FH) {
// Fallback; should never be reached if tests above are good enough!
$this->dropError('Net_LDAP2_LDIF error: Could not open file '.$file);
} else {
$this->_FH_opened = true;
}
}
}
}
}
/**
* Read one entry from the file and return it as a Net::LDAP::Entry object.
*
* @return Net_LDAP2_Entry
*/
public function read_entry()
{
// read fresh lines, set them as current lines and create the entry
$attrs = $this->next_lines(true);
if (count($attrs) > 0) {
$this->_lines_cur = $attrs;
}
return $this->current_entry();
}
/**
* Returns true when the end of the file is reached.
*
* @return boolean
*/
public function eof()
{
return feof($this->_FH);
}
/**
* Write the entry or entries to the LDIF file.
*
* If you want to build an LDIF file containing several entries AND
* you want to call write_entry() several times, you must open the filehandle
* in append mode ("a"), otherwise you will always get the last entry only.
*
* @param Net_LDAP2_Entry|array $entries Entry or array of entries
*
* @return void
* @todo implement operations on whole entries (adding a whole entry)
*/
public function write_entry($entries)
{
if (!is_array($entries)) {
$entries = array($entries);
}
foreach ($entries as $entry) {
$this->_entrynum++;
if (!$entry instanceof Net_LDAP2_Entry) {
$this->dropError('Net_LDAP2_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP2_Entry object');
} else {
if ($this->_options['change']) {
// LDIF change mode
// fetch change information from entry
$entry_attrs_changes = $entry->getChanges();
$num_of_changes = count($entry_attrs_changes['add'])
+ count($entry_attrs_changes['replace'])
+ count($entry_attrs_changes['delete']);
$is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
// write version if not done yet
// also write DN of entry
if ($is_changed) {
if (!$this->_version_written) {
$this->write_version();
}
$this->writeDN($entry->currentDN());
}
// process changes
// TODO: consider DN add!
if ($entry->willBeDeleted()) {
$this->writeLine("changetype: delete".PHP_EOL);
} elseif ($entry->willBeMoved()) {
$this->writeLine("changetype: modrdn".PHP_EOL);
$olddn = Net_LDAP2_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
$oldrdn = array_shift($olddn);
$oldparent = implode(',', $olddn);
$newdn = Net_LDAP2_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
$rdn = array_shift($newdn);
$parent = implode(',', $newdn);
$this->writeLine("newrdn: ".$rdn.PHP_EOL);
$this->writeLine("deleteoldrdn: 1".PHP_EOL);
if ($parent !== $oldparent) {
$this->writeLine("newsuperior: ".$parent.PHP_EOL);
}
// TODO: What if the entry has attribute changes as well?
// I think we should check for that and make a dummy
// entry with the changes that is written to the LDIF file
} elseif ($num_of_changes > 0) {
// write attribute change data
$this->writeLine("changetype: modify".PHP_EOL);
foreach ($entry_attrs_changes as $changetype => $entry_attrs) {
foreach ($entry_attrs as $attr_name => $attr_values) {
$this->writeLine("$changetype: $attr_name".PHP_EOL);
if ($attr_values !== null) $this->writeAttribute($attr_name, $attr_values, $changetype);
$this->writeLine("-".PHP_EOL);
}
}
}
// finish this entrys data if we had changes
if ($is_changed) {
$this->finishEntry();
}
} else {
// LDIF-content mode
// fetch attributes for further processing
$entry_attrs = $entry->getValues();
// sort and put objectclass-attrs to first position
if ($this->_options['sort']) {
ksort($entry_attrs);
if (array_key_exists('objectclass', $entry_attrs)) {
$oc = $entry_attrs['objectclass'];
unset($entry_attrs['objectclass']);
$entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs);
}
}
// write data
if (!$this->_version_written) {
$this->write_version();
}
$this->writeDN($entry->dn());
foreach ($entry_attrs as $attr_name => $attr_values) {
$this->writeAttribute($attr_name, $attr_values);
}
$this->finishEntry();
}
}
}
}
/**
* Write version to LDIF
*
* If the object's version is defined, this method allows to explicitely write the version before an entry is written.
* If not called explicitely, it gets called automatically when writing the first entry.
*
* @return void
*/
public function write_version()
{
$this->_version_written = true;
if (!is_null($this->version())) {
return $this->writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP2_LDIF error: unable to write version');
}
}
/**
* Get or set LDIF version
*
* If called without arguments it returns the version of the LDIF file or NULL if no version has been set.
* If called with an argument it sets the LDIF version to VERSION.
* According to RFC 2849 currently the only legal value for VERSION is 1.
*
* @param int $version (optional) LDIF version to set
*
* @return int
*/
public function version($version = null)
{
if ($version !== null) {
if ($version != 1) {
$this->dropError('Net_LDAP2_LDIF error: illegal LDIF version set');
} else {
$this->_options['version'] = $version;
}
}
return $this->_options['version'];
}
/**
* Returns the file handle the Net_LDAP2_LDIF object reads from or writes to.
*
* You can, for example, use this to fetch the content of the LDIF file yourself
*
* @return null|resource
*/
public function &handle()
{
if (!is_resource($this->_FH)) {
$this->dropError('Net_LDAP2_LDIF error: invalid file resource');
$null = null;
return $null;
} else {
return $this->_FH;
}
}
/**
* Clean up
*
* This method signals that the LDIF object is no longer needed.
* You can use this to free up some memory and close the file handle.
* The file handle is only closed, if it was opened from Net_LDAP2_LDIF.
*
* @return void
*/
public function done()
{
// close FH if we opened it
if ($this->_FH_opened) {
fclose($this->handle());
}
// free variables
foreach (get_object_vars($this) as $name => $value) {
unset($this->$name);
}
}
/**
* Returns last error message if error was found.
*
* Example:
* <code>
* $ldif->someAction();
* if ($ldif->error()) {
* echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines();
* }
* </code>
*
* @param boolean $as_string If set to true, only the message is returned
*
* @return false|Net_LDAP2_Error
*/
public function error($as_string = false)
{
if (Net_LDAP2::isError($this->_error['error'])) {
return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error'];
} else {
return false;
}
}
/**
* Returns lines that resulted in error.
*
* Perl returns an array of faulty lines in list context,
* but we always just return an int because of PHPs language.
*
* @return int
*/
public function error_lines()
{
return $this->_error['line'];
}
/**
* Returns the current Net::LDAP::Entry object.
*
* @return Net_LDAP2_Entry|false
*/
public function current_entry()
{
return $this->parseLines($this->current_lines());
}
/**
* Parse LDIF lines of one entry into an Net_LDAP2_Entry object
*
* @param array $lines LDIF lines for one entry
*
* @return Net_LDAP2_Entry|false Net_LDAP2_Entry object for those lines
* @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
*/
public function parseLines($lines)
{
// parse lines into an array of attributes and build the entry
$attributes = array();
$dn = false;
foreach ($lines as $line) {
if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
$attr =& $matches[1] . $matches[2];
$delim =& $matches[3];
$data =& $matches[4];
if ($delim == ':') {
// normal data
$attributes[$attr][] = $data;
} elseif ($delim == '::') {
// base64 data
$attributes[$attr][] = base64_decode($data);
} elseif ($delim == ':<') {
// file inclusion
// TODO: Is this the job of the LDAP-client or the server?
$this->dropError('File inclusions are currently not supported');
//$attributes[$attr][] = ...;
} else {
// since the pattern above, the delimeter cannot be something else.
$this->dropError('Net_LDAP2_LDIF parsing error: invalid syntax at parsing entry line: '.$line);
continue;
}
if (strtolower($attr) == 'dn') {
// DN line detected
$dn = $attributes[$attr][0]; // save possibly decoded DN
unset($attributes[$attr]); // remove wrongly added "dn: " attribute
}
} else {
// line not in "attr: value" format -> ignore
// maybe we should rise an error here, but this should be covered by
// next_lines() already. A problem arises, if users try to feed data of
// several entries to this method - the resulting entry will
// get wrong attributes. However, this is already mentioned in the
// methods documentation above.
}
}
if (false === $dn) {
$this->dropError('Net_LDAP2_LDIF parsing error: unable to detect DN for entry');
return false;
} else {
$newentry = Net_LDAP2_Entry::createFresh($dn, $attributes);
return $newentry;
}
}
/**
* Returns the lines that generated the current Net::LDAP::Entry object.
*
* Note that this returns an empty array if no lines have been read so far.
*
* @return array Array of lines
*/
public function current_lines()
{
return $this->_lines_cur;
}
/**
* Returns the lines that will generate the next Net::LDAP::Entry object.
*
* If you set $force to TRUE then you can iterate over the lines that build
* up entries manually. Otherwise, iterating is done using {@link read_entry()}.
* Force will move the file pointer forward, thus returning the next entries lines.
*
* Wrapped lines will be unwrapped. Comments are stripped.
*
* @param boolean $force Set this to true if you want to iterate over the lines manually
*
* @return array
*/
public function next_lines($force = false)
{
// if we already have those lines, just return them, otherwise read
if (count($this->_lines_next) == 0 || $force) {
$this->_lines_next = array(); // empty in case something was left (if used $force)
$entry_done = false;
$fh = &$this->handle();
$commentmode = false; // if we are in an comment, for wrapping purposes
$datalines_read = 0; // how many lines with data we have read
while (!$entry_done && !$this->eof()) {
$this->_input_line++;
// Read line. Remove line endings, we want only data;
// this is okay since ending spaces should be encoded
$data = rtrim(fgets($fh));
if ($data === false) {
// error only, if EOF not reached after fgets() call
if (!$this->eof()) {
$this->dropError('Net_LDAP2_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line);
}
break;
} else {
if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) {
// Entry is finished if we have an empty line after we had data
$entry_done = true;
// Look ahead if the next EOF is nearby. Comments and empty
// lines at the file end may cause problems otherwise
$current_pos = ftell($fh);
$data = fgets($fh);
while (!feof($fh)) {
if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) {
// only empty lines or comments, continue to seek
// TODO: Known bug: Wrappings for comments are okay but are treaten as
// error, since we do not honor comment mode here.
// This should be a very theoretically case, however
// i am willing to fix this if really necessary.
$this->_input_line++;
$current_pos = ftell($fh);
$data = fgets($fh);
} else {
// Data found if non emtpy line and not a comment!!
// Rewind to position prior last read and stop lookahead
fseek($fh, $current_pos);
break;
}
}
// now we have either the file pointer at the beginning of
// a new data position or at the end of file causing feof() to return true
} else {
// build lines
if (preg_match('/^version:\s(.+)$/', $data, $match)) {
// version statement, set version
$this->version($match[1]);
} elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
// normal attribute: add line
$commentmode = false;
$this->_lines_next[] = trim($data);
$datalines_read++;
} elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
// wrapped data: unwrap if not in comment mode
// note that the \s above is some more liberal than
// the RFC requests as it also matches tabs etc.
if (!$commentmode) {
if ($datalines_read == 0) {
// first line of entry: wrapped data is illegal
$this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
} else {
$last = array_pop($this->_lines_next);
$last = $last.$matches[1];
$this->_lines_next[] = $last;
$datalines_read++;
}
}
} elseif (preg_match('/^#/', $data)) {
// LDIF comments
$commentmode = true;
} elseif (preg_match('/^\s*$/', $data)) {
// empty line but we had no data for this
// entry, so just ignore this line
$commentmode = false;
} else {
$this->dropError('Net_LDAP2_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line);
continue;
}
}
}
}
}
return $this->_lines_next;
}
/**
* Convert an attribute and value to LDIF string representation
*
* It honors correct encoding of values according to RFC 2849.
* Line wrapping will occur at the configured maximum but only if
* the value is greater than 40 chars.
*
* @param string $attr_name Name of the attribute
* @param string $attr_value Value of the attribute
*
* @access protected
* @return string LDIF string for that attribute and value
*/
protected function convertAttribute($attr_name, $attr_value)
{
// Handle empty attribute or process
if (strlen($attr_value) == 0) {
$attr_value = " ";
} else {
$base64 = false;
// ASCII-chars that are NOT safe for the
// start and for being inside the value.
// These are the int values of those chars.
$unsafe_init = array(0, 10, 13, 32, 58, 60);
$unsafe = array(0, 10, 13);
// Test for illegal init char
$init_ord = ord(substr($attr_value, 0, 1));
if ($init_ord > 127 || in_array($init_ord, $unsafe_init)) {
$base64 = true;
}
// Test for illegal content char
for ($i = 0; $i < strlen($attr_value); $i++) {
$char_ord = ord(substr($attr_value, $i, 1));
if ($char_ord > 127 || in_array($char_ord, $unsafe)) {
$base64 = true;
}
}
// Test for ending space
if (substr($attr_value, -1) == ' ') {
$base64 = true;
}
// If converting is needed, do it
// Either we have some special chars or a matching "raw" regex
if ($base64 || ($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) {
$attr_name .= ':';
$attr_value = base64_encode($attr_value);
}
// Lowercase attr names if requested
if ($this->_options['lowercase']) $attr_name = strtolower($attr_name);
// Handle line wrapping
if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) {
$attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true);
}
}
return $attr_name.': '.$attr_value;
}
/**
* Convert an entries DN to LDIF string representation
*
* It honors correct encoding of values according to RFC 2849.
*
* @param string $dn UTF8-Encoded DN
*
* @access protected
* @return string LDIF string for that DN
* @todo I am not sure, if the UTF8 stuff is correctly handled right now
*/
protected function convertDN($dn)
{
$base64 = false;
// ASCII-chars that are NOT safe for the
// start and for being inside the dn.
// These are the int values of those chars.
$unsafe_init = array(0, 10, 13, 32, 58, 60);
$unsafe = array(0, 10, 13);
// Test for illegal init char
$init_ord = ord(substr($dn, 0, 1));
if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) {
$base64 = true;
}
// Test for illegal content char
for ($i = 0; $i < strlen($dn); $i++) {
$char = substr($dn, $i, 1);
if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
$base64 = true;
}
}
// Test for ending space
if (substr($dn, -1) == ' ') {
$base64 = true;
}
// if converting is needed, do it
return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn;
}
/**
* Writes an attribute to the filehandle
*
* @param string $attr_name Name of the attribute
* @param string|array $attr_values Single attribute value or array with attribute values
*
* @access protected
* @return void
*/
protected function writeAttribute($attr_name, $attr_values)
{
// write out attribute content
if (!is_array($attr_values)) {
$attr_values = array($attr_values);
}
foreach ($attr_values as $attr_val) {
$line = $this->convertAttribute($attr_name, $attr_val).PHP_EOL;
$this->writeLine($line, 'Net_LDAP2_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum);
}
}
/**
* Writes a DN to the filehandle
*
* @param string $dn DN to write
*
* @access protected
* @return void
*/
protected function writeDN($dn)
{
// prepare DN
if ($this->_options['encode'] == 'base64') {
$dn = $this->convertDN($dn).PHP_EOL;
} elseif ($this->_options['encode'] == 'canonical') {
$dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
} else {
$dn = $dn.PHP_EOL;
}
$this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry '.$this->_entrynum);
}
/**
* Finishes an LDIF entry
*
* @access protected
* @return void
*/
protected function finishEntry()
{
$this->writeLine(PHP_EOL, 'Net_LDAP2_LDIF error: unable to close entry '.$this->_entrynum);
}
/**
* Just write an arbitary line to the filehandle
*
* @param string $line Content to write
* @param string $error If error occurs, drop this message
*
* @access protected
* @return true|false
*/
protected function writeLine($line, $error = 'Net_LDAP2_LDIF error: unable to write to filehandle')
{
if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) {
$this->dropError($error);
return false;
} else {
return true;
}
}
/**
* Optionally raises an error and pushes the error on the error cache
*
* @param string $msg Errortext
* @param int $line Line in the LDIF that caused the error
*
* @access protected
* @return void
*/
protected function dropError($msg, $line = null)
{
$this->_error['error'] = new Net_LDAP2_Error($msg);
if ($line !== null) $this->_error['line'] = $line;
if ($this->_options['onerror'] == 'die') {
die($msg.PHP_EOL);
} elseif ($this->_options['onerror'] == 'warn') {
echo $msg.PHP_EOL;
}
}
}
?>
+240
View File
@@ -0,0 +1,240 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_RootDSE interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Jan Wagner <wagner@netsols.de>
* @copyright 2009 Jan Wagner
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Getting the rootDSE entry of a LDAP server
*
* @category Net
* @package Net_LDAP2
* @author Jan Wagner <wagner@netsols.de>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_RootDSE extends PEAR
{
/**
* @access protected
* @var object Net_LDAP2_Entry
**/
protected $_entry;
/**
* Class constructor
*
* @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry object of the RootDSE
*/
public function __construct(&$entry)
{
$this->_entry = $entry;
}
/**
* Fetches a RootDSE object from an LDAP connection
*
* @param Net_LDAP2 $ldap Directory from which the RootDSE should be fetched
* @param array $attrs Array of attributes to search for
*
* @access static
* @return Net_LDAP2_RootDSE|Net_LDAP2_Error
*/
public static function fetch($ldap, $attrs = null)
{
if (!$ldap instanceof Net_LDAP2) {
return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
}
if (is_array($attrs) && count($attrs) > 0 ) {
$attributes = $attrs;
} else {
$attributes = array('vendorName',
'vendorVersion',
'namingContexts',
'altServer',
'supportedExtension',
'supportedControl',
'supportedSASLMechanisms',
'supportedLDAPVersion',
'subschemaSubentry' );
}
$result = $ldap->search('', '(objectClass=*)', array('attributes' => $attributes, 'scope' => 'base'));
if (self::isError($result)) {
return $result;
}
$entry = $result->shiftEntry();
if (false === $entry) {
return PEAR::raiseError('Could not fetch RootDSE entry');
}
$ret = new Net_LDAP2_RootDSE($entry);
return $ret;
}
/**
* Gets the requested attribute value
*
* Same usuage as {@link Net_LDAP2_Entry::getValue()}
*
* @param string $attr Attribute name
* @param array $options Array of options
*
* @access public
* @return mixed Net_LDAP2_Error object or attribute values
* @see Net_LDAP2_Entry::get_value()
*/
public function getValue($attr = '', $options = '')
{
return $this->_entry->get_value($attr, $options);
}
/**
* Alias function of getValue() for perl-ldap interface
*
* @see getValue()
* @return mixed
*/
public function get_value()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'getValue' ), $args);
}
/**
* Determines if the extension is supported
*
* @param array $oids Array of oids to check
*
* @access public
* @return boolean
*/
public function supportedExtension($oids)
{
return $this->checkAttr($oids, 'supportedExtension');
}
/**
* Alias function of supportedExtension() for perl-ldap interface
*
* @see supportedExtension()
* @return boolean
*/
public function supported_extension()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'supportedExtension'), $args);
}
/**
* Determines if the version is supported
*
* @param array $versions Versions to check
*
* @access public
* @return boolean
*/
public function supportedVersion($versions)
{
return $this->checkAttr($versions, 'supportedLDAPVersion');
}
/**
* Alias function of supportedVersion() for perl-ldap interface
*
* @see supportedVersion()
* @return boolean
*/
public function supported_version()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedVersion'), $args);
}
/**
* Determines if the control is supported
*
* @param array $oids Control oids to check
*
* @access public
* @return boolean
*/
public function supportedControl($oids)
{
return $this->checkAttr($oids, 'supportedControl');
}
/**
* Alias function of supportedControl() for perl-ldap interface
*
* @see supportedControl()
* @return boolean
*/
public function supported_control()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedControl' ), $args);
}
/**
* Determines if the sasl mechanism is supported
*
* @param array $mechlist SASL mechanisms to check
*
* @access public
* @return boolean
*/
public function supportedSASLMechanism($mechlist)
{
return $this->checkAttr($mechlist, 'supportedSASLMechanisms');
}
/**
* Alias function of supportedSASLMechanism() for perl-ldap interface
*
* @see supportedSASLMechanism()
* @return boolean
*/
public function supported_sasl_mechanism()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedSASLMechanism'), $args);
}
/**
* Checks for existance of value in attribute
*
* @param array $values values to check
* @param string $attr attribute name
*
* @access protected
* @return boolean
*/
protected function checkAttr($values, $attr)
{
if (!is_array($values)) $values = array($values);
foreach ($values as $value) {
if (!@in_array($value, $this->get_value($attr, 'all'))) {
return false;
}
}
return true;
}
}
?>
+622
View File
@@ -0,0 +1,622 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Schema interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Jan Wagner <wagner@netsols.de>
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Jan Wagner, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
* @todo see the comment at the end of the file
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Syntax definitions
*
* Please don't forget to add binary attributes to isBinary() below
* to support proper value fetching from Net_LDAP2_Entry
*/
define('NET_LDAP2_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7');
define('NET_LDAP2_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15');
define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
define('NET_LDAP2_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27');
define('NET_LDAP2_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28');
define('NET_LDAP2_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36');
define('NET_LDAP2_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38');
define('NET_LDAP2_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40');
/**
* Load an LDAP Schema and provide information
*
* This class takes a Subschema entry, parses this information
* and makes it available in an array. Most of the code has been
* inspired by perl-ldap( http://perl-ldap.sourceforge.net).
* You will find portions of their implementation in here.
*
* @category Net
* @package Net_LDAP2
* @author Jan Wagner <wagner@netsols.de>
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Schema extends PEAR
{
/**
* Map of entry types to ldap attributes of subschema entry
*
* @access public
* @var array
*/
public $types = array(
'attribute' => 'attributeTypes',
'ditcontentrule' => 'dITContentRules',
'ditstructurerule' => 'dITStructureRules',
'matchingrule' => 'matchingRules',
'matchingruleuse' => 'matchingRuleUse',
'nameform' => 'nameForms',
'objectclass' => 'objectClasses',
'syntax' => 'ldapSyntaxes'
);
/**
* Array of entries belonging to this type
*
* @access protected
* @var array
*/
protected $_attributeTypes = array();
protected $_matchingRules = array();
protected $_matchingRuleUse = array();
protected $_ldapSyntaxes = array();
protected $_objectClasses = array();
protected $_dITContentRules = array();
protected $_dITStructureRules = array();
protected $_nameForms = array();
/**
* hash of all fetched oids
*
* @access protected
* @var array
*/
protected $_oids = array();
/**
* Tells if the schema is initialized
*
* @access protected
* @var boolean
* @see parse(), get()
*/
protected $_initialized = false;
/**
* Constructor of the class
*
* @access protected
*/
public function __construct()
{
parent::__construct('Net_LDAP2_Error'); // default error class
}
/**
* Fetch the Schema from an LDAP connection
*
* @param Net_LDAP2 $ldap LDAP connection
* @param string $dn (optional) Subschema entry dn
*
* @access public
* @return Net_LDAP2_Schema|NET_LDAP2_Error
*/
public static function fetch($ldap, $dn = null)
{
if (!$ldap instanceof Net_LDAP2) {
return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
}
$schema_o = new Net_LDAP2_Schema();
if (is_null($dn)) {
// get the subschema entry via root dse
$dse = $ldap->rootDSE(array('subschemaSubentry'));
if (false == Net_LDAP2::isError($dse)) {
$base = $dse->getValue('subschemaSubentry', 'single');
if (!Net_LDAP2::isError($base)) {
$dn = $base;
}
}
}
// Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly
// call this entry subSchemaSubentry instead of subschemaSubentry.
// Note the correct case/spelling as per RFC 2251.
if (is_null($dn)) {
// get the subschema entry via root dse
$dse = $ldap->rootDSE(array('subSchemaSubentry'));
if (false == Net_LDAP2::isError($dse)) {
$base = $dse->getValue('subSchemaSubentry', 'single');
if (!Net_LDAP2::isError($base)) {
$dn = $base;
}
}
}
// Final fallback case where there is no subschemaSubentry attribute
// in the root DSE (this is a bug for an LDAP v3 server so report this
// to your LDAP vendor if you get this far).
if (is_null($dn)) {
$dn = 'cn=Subschema';
}
// fetch the subschema entry
$result = $ldap->search($dn, '(objectClass=*)',
array('attributes' => array_values($schema_o->types),
'scope' => 'base'));
if (Net_LDAP2::isError($result)) {
return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());
}
$entry = $result->shiftEntry();
if (!$entry instanceof Net_LDAP2_Entry) {
if ($entry instanceof Net_LDAP2_Error) {
return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());
} else {
return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');
}
}
$schema_o->parse($entry);
return $schema_o;
}
/**
* Return a hash of entries for the given type
*
* Returns a hash of entry for the givene type. Types may be:
* objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
* matchingruleuses, nameforms, syntaxes
*
* @param string $type Type to fetch
*
* @access public
* @return array|Net_LDAP2_Error Array or Net_LDAP2_Error
*/
public function &getAll($type)
{
$map = array('objectclasses' => &$this->_objectClasses,
'attributes' => &$this->_attributeTypes,
'ditcontentrules' => &$this->_dITContentRules,
'ditstructurerules' => &$this->_dITStructureRules,
'matchingrules' => &$this->_matchingRules,
'matchingruleuses' => &$this->_matchingRuleUse,
'nameforms' => &$this->_nameForms,
'syntaxes' => &$this->_ldapSyntaxes );
$key = strtolower($type);
$ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
return $ret;
}
/**
* Return a specific entry
*
* @param string $type Type of name
* @param string $name Name or OID to fetch
*
* @access public
* @return mixed Entry or Net_LDAP2_Error
*/
public function &get($type, $name)
{
if ($this->_initialized) {
$type = strtolower($type);
if (false == key_exists($type, $this->types)) {
return PEAR::raiseError("No such type $type");
}
$name = strtolower($name);
$type_var = &$this->{'_' . $this->types[$type]};
if (key_exists($name, $type_var)) {
return $type_var[$name];
} elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
return $this->_oids[$name];
} else {
return PEAR::raiseError("Could not find $type $name");
}
} else {
$return = null;
return $return;
}
}
/**
* Fetches attributes that MAY be present in the given objectclass
*
* @param string $oc Name or OID of objectclass
*
* @access public
* @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
*/
public function may($oc)
{
return $this->_getAttr($oc, 'may');
}
/**
* Fetches attributes that MUST be present in the given objectclass
*
* @param string $oc Name or OID of objectclass
*
* @access public
* @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
*/
public function must($oc)
{
return $this->_getAttr($oc, 'must');
}
/**
* Fetches the given attribute from the given objectclass
*
* @param string $oc Name or OID of objectclass
* @param string $attr Name of attribute to fetch
*
* @access protected
* @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error
*/
protected function _getAttr($oc, $attr)
{
$oc = strtolower($oc);
if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
return $this->_objectClasses[$oc][$attr];
} elseif (key_exists($oc, $this->_oids) &&
$this->_oids[$oc]['type'] == 'objectclass' &&
key_exists($attr, $this->_oids[$oc])) {
return $this->_oids[$oc][$attr];
} else {
return PEAR::raiseError("Could not find $attr attributes for $oc ");
}
}
/**
* Returns the name(s) of the immediate superclass(es)
*
* @param string $oc Name or OID of objectclass
*
* @access public
* @return array|Net_LDAP2_Error Array of names or Net_LDAP2_Error
*/
public function superclass($oc)
{
$o = $this->get('objectclass', $oc);
if (Net_LDAP2::isError($o)) {
return $o;
}
return (key_exists('sup', $o) ? $o['sup'] : array());
}
/**
* Parses the schema of the given Subschema entry
*
* @param Net_LDAP2_Entry &$entry Subschema entry
*
* @access public
* @return void
*/
public function parse(&$entry)
{
foreach ($this->types as $type => $attr) {
// initialize map type to entry
$type_var = '_' . $attr;
$this->{$type_var} = array();
// get values for this type
if ($entry->exists($attr)) {
$values = $entry->getValue($attr);
if (is_array($values)) {
foreach ($values as $value) {
unset($schema_entry); // this was a real mess without it
// get the schema entry
$schema_entry = $this->_parse_entry($value);
// set the type
$schema_entry['type'] = $type;
// save a ref in $_oids
$this->_oids[$schema_entry['oid']] = &$schema_entry;
// save refs for all names in type map
$names = $schema_entry['aliases'];
array_push($names, $schema_entry['name']);
foreach ($names as $name) {
$this->{$type_var}[strtolower($name)] = &$schema_entry;
}
}
}
}
}
$this->_initialized = true;
}
/**
* Parses an attribute value into a schema entry
*
* @param string $value Attribute value
*
* @access protected
* @return array|false Schema entry array or false
*/
protected function &_parse_entry($value)
{
// tokens that have no value associated
$noValue = array('single-value',
'obsolete',
'collective',
'no-user-modification',
'abstract',
'structural',
'auxiliary');
// tokens that can have multiple values
$multiValue = array('must', 'may', 'sup');
$schema_entry = array('aliases' => array()); // initilization
$tokens = $this->_tokenize($value); // get an array of tokens
// remove surrounding brackets
if ($tokens[0] == '(') array_shift($tokens);
if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(
$schema_entry['oid'] = array_shift($tokens); // first token is the oid
// cycle over the tokens until none are left
while (count($tokens) > 0) {
$token = strtolower(array_shift($tokens));
if (in_array($token, $noValue)) {
$schema_entry[$token] = 1; // single value token
} else {
// this one follows a string or a list if it is multivalued
if (($schema_entry[$token] = array_shift($tokens)) == '(') {
// this creates the list of values and cycles through the tokens
// until the end of the list is reached ')'
$schema_entry[$token] = array();
while ($tmp = array_shift($tokens)) {
if ($tmp == ')') break;
if ($tmp != '$') array_push($schema_entry[$token], $tmp);
}
}
// create a array if the value should be multivalued but was not
if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
$schema_entry[$token] = array($schema_entry[$token]);
}
}
}
// get max length from syntax
if (key_exists('syntax', $schema_entry)) {
if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
$schema_entry['max_length'] = $matches[1];
}
}
// force a name
if (empty($schema_entry['name'])) {
$schema_entry['name'] = $schema_entry['oid'];
}
// make one name the default and put the other ones into aliases
if (is_array($schema_entry['name'])) {
$aliases = $schema_entry['name'];
$schema_entry['name'] = array_shift($aliases);
$schema_entry['aliases'] = $aliases;
}
return $schema_entry;
}
/**
* Tokenizes the given value into an array of tokens
*
* @param string $value String to parse
*
* @access protected
* @return array Array of tokens
*/
protected function _tokenize($value)
{
$tokens = array(); // array of tokens
$matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns
// this one is taken from perl-ldap, modified for php
$pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";
/**
* This one matches one big pattern wherin only one of the three subpatterns matched
* We are interested in the subpatterns that matched. If it matched its value will be
* non-empty and so it is a token. Tokens may be round brackets, a string, or a string
* enclosed by '
*/
preg_match_all($pattern, $value, $matches);
for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match)
for ($j = 1; $j < 4; $j++) { // each subpattern
if (null != trim($matches[$j][$i])) { // pattern match in this subpattern
$tokens[$i] = trim($matches[$j][$i]); // this is the token
}
}
}
return $tokens;
}
/**
* Returns wether a attribute syntax is binary or not
*
* This method gets used by Net_LDAP2_Entry to decide which
* PHP function needs to be used to fetch the value in the
* proper format (e.g. binary or string)
*
* @param string $attribute The name of the attribute (eg.: 'sn')
*
* @access public
* @return boolean
*/
public function isBinary($attribute)
{
$return = false; // default to false
// This list contains all syntax that should be treaten as
// containing binary values
// The Syntax Definitons go into constants at the top of this page
$syntax_binary = array(
NET_LDAP2_SYNTAX_OCTET_STRING,
NET_LDAP2_SYNTAX_JPEG
);
// Check Syntax
$attr_s = $this->get('attribute', $attribute);
if (Net_LDAP2::isError($attr_s)) {
// Attribute not found in schema
$return = false; // consider attr not binary
} elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
// Syntax is defined as binary in schema
$return = true;
} else {
// Syntax not defined as binary, or not found
// if attribute is a subtype, check superior attribute syntaxes
if (isset($attr_s['sup'])) {
foreach ($attr_s['sup'] as $superattr) {
$return = $this->isBinary($superattr);
if ($return) {
break; // stop checking parents since we are binary
}
}
}
}
return $return;
}
/**
* See if an schema element exists
*
* @param string $type Type of name, see get()
* @param string $name Name or OID
*
* @return boolean
*/
public function exists($type, $name)
{
$entry = $this->get($type, $name);
if ($entry instanceof Net_LDAP2_ERROR) {
return false;
} else {
return true;
}
}
/**
* See if an attribute is defined in the schema
*
* @param string $attribute Name or OID of the attribute
* @return boolean
*/
public function attributeExists($attribute)
{
return $this->exists('attribute', $attribute);
}
/**
* See if an objectClass is defined in the schema
*
* @param string $ocl Name or OID of the objectClass
* @return boolean
*/
public function objectClassExists($ocl)
{
return $this->exists('objectclass', $ocl);
}
/**
* See to which ObjectClasses an attribute is assigned
*
* The objectclasses are sorted into the keys 'may' and 'must'.
*
* @param string $attribute Name or OID of the attribute
*
* @return array|Net_LDAP2_Error Associative array with OCL names or Error
*/
public function getAssignedOCLs($attribute)
{
$may = array();
$must = array();
// Test if the attribute type is defined in the schema,
// if so, retrieve real name for lookups
$attr_entry = $this->get('attribute', $attribute);
if ($attr_entry instanceof Net_LDAP2_ERROR) {
return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());
} else {
$attribute = $attr_entry['name'];
}
// We need to get all defined OCLs for this.
$ocls = $this->getAll('objectclasses');
foreach ($ocls as $ocl => $ocl_data) {
// Fetch the may and must attrs and see if our searched attr is contained.
// If so, record it in the corresponding array.
$ocl_may_attrs = $this->may($ocl);
$ocl_must_attrs = $this->must($ocl);
if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
array_push($may, $ocl_data['name']);
}
if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
array_push($must, $ocl_data['name']);
}
}
return array('may' => $may, 'must' => $must);
}
/**
* See if an attribute is available in a set of objectClasses
*
* @param string $attribute Attribute name or OID
* @param array $ocls Names of OCLs to check for
*
* @return boolean TRUE, if the attribute is defined for at least one of the OCLs
*/
public function checkAttribute($attribute, $ocls)
{
foreach ($ocls as $ocl) {
$ocl_entry = $this->get('objectclass', $ocl);
$ocl_may_attrs = $this->may($ocl);
$ocl_must_attrs = $this->must($ocl);
if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
return true;
}
if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
return true;
}
}
return false; // no ocl for the ocls found.
}
}
?>
@@ -0,0 +1,59 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_SchemaCache interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Interface describing a custom schema cache object
*
* To implement a custom schema cache, one must implement this interface and
* pass the instanciated object to Net_LDAP2s registerSchemaCache() method.
*/
interface Net_LDAP2_SchemaCache
{
/**
* Return the schema object from the cache
*
* Net_LDAP2 will consider anything returned invalid, except
* a valid Net_LDAP2_Schema object.
* In case you return a Net_LDAP2_Error, this error will be routed
* to the return of the $ldap->schema() call.
* If you return something else, Net_LDAP2 will
* fetch a fresh Schema object from the LDAP server.
*
* You may want to implement a cache aging mechanism here too.
*
* @return Net_LDAP2_Schema|Net_LDAP2_Error|false
*/
public function loadSchema();
/**
* Store a schema object in the cache
*
* This method will be called, if Net_LDAP2 has fetched a fresh
* schema object from LDAP and wants to init or refresh the cache.
*
* In case of errors you may return a Net_LDAP2_Error which will
* be routet to the client.
* Note that doing this prevents, that the schema object fetched from LDAP
* will be given back to the client, so only return errors if storing
* of the cache is something crucial (e.g. for doing something else with it).
* Normaly you dont want to give back errors in which case Net_LDAP2 needs to
* fetch the schema once per script run and instead use the error
* returned from loadSchema().
*
* @return true|Net_LDAP2_Error
*/
public function storeSchema($schema);
}
+631
View File
@@ -0,0 +1,631 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Search interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Tarjej Huse <tarjei@bergfald.no>
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Result set of an LDAP search
*
* @category Net
* @package Net_LDAP2
* @author Tarjej Huse <tarjei@bergfald.no>
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Search extends PEAR implements Iterator
{
/**
* Search result identifier
*
* @access protected
* @var resource
*/
protected $_search;
/**
* LDAP resource link
*
* @access protected
* @var resource
*/
protected $_link;
/**
* Net_LDAP2 object
*
* A reference of the Net_LDAP2 object for passing to Net_LDAP2_Entry
*
* @access protected
* @var object Net_LDAP2
*/
protected $_ldap;
/**
* Result entry identifier
*
* @access protected
* @var resource
*/
protected $_entry = null;
/**
* The errorcode the search got
*
* Some errorcodes might be of interest, but might not be best handled as errors.
* examples: 4 - LDAP_SIZELIMIT_EXCEEDED - indicates a huge search.
* Incomplete results are returned. If you just want to check if there's anything in the search.
* than this is a point to handle.
* 32 - no such object - search here returns a count of 0.
*
* @access protected
* @var int
*/
protected $_errorCode = 0; // if not set - sucess!
/**
* Cache for all entries already fetched from iterator interface
*
* @access protected
* @var array
*/
protected $_iteratorCache = array();
/**
* What attributes we searched for
*
* The $attributes array contains the names of the searched attributes and gets
* passed from $Net_LDAP2->search() so the Net_LDAP2_Search object can tell
* what attributes was searched for ({@link searchedAttrs())
*
* This variable gets set from the constructor and returned
* from {@link searchedAttrs()}
*
* @access protected
* @var array
*/
protected $_searchedAttrs = array();
/**
* Cache variable for storing entries fetched internally
*
* This currently is not used by all functions and need consolidation.
*
* @access protected
* @var array
*/
protected $_entry_cache = false;
/**
* Cache variable for count()
*
* @see count()
* @access protected
* @var int
*/
protected $_count_cache = null;
/**
* Constructor
*
* @param resource $search Search result identifier
* @param Net_LDAP2|resource $ldap Net_LDAP2 object or just a LDAP-Link resource
* @param array $attributes (optional) Array with searched attribute names. (see {@link $_searchedAttrs})
*
* @access public
*/
public function __construct($search, $ldap, $attributes = array())
{
parent::__construct('Net_LDAP2_Error');
$this->setSearch($search);
if ($ldap instanceof Net_LDAP2) {
$this->_ldap = $ldap;
$this->setLink($this->_ldap->getLink());
} else {
$this->setLink($ldap);
}
$this->_errorCode = @ldap_errno($this->_link);
if (is_array($attributes) && !empty($attributes)) {
$this->_searchedAttrs = $attributes;
}
}
/**
* Returns an array of entry objects.
*
* @return array Array of entry objects.
*/
public function entries()
{
$entries = array();
if (false === $this->_entry_cache) {
// cache is empty: fetch from LDAP
while ($entry = $this->shiftEntry()) {
$entries[] = $entry;
}
$this->_entry_cache = $entries; // store result in cache
}
return $this->_entry_cache;
}
/**
* Get the next entry in the searchresult from LDAP server.
*
* This will return a valid Net_LDAP2_Entry object or false, so
* you can use this method to easily iterate over the entries inside
* a while loop.
*
* @return Net_LDAP2_Entry|false Reference to Net_LDAP2_Entry object or false
*/
public function shiftEntry()
{
if (is_null($this->_entry)) {
if(!$this->_entry = @ldap_first_entry($this->_link, $this->_search)) {
$false = false;
return $false;
}
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
if ($entry instanceof PEAR_Error) $entry = false;
} else {
if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
$false = false;
return $false;
}
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
if ($entry instanceof PEAR_Error) $entry = false;
}
return $entry;
}
/**
* Alias function of shiftEntry() for perl-ldap interface
*
* @see shiftEntry()
* @return Net_LDAP2_Entry|false
*/
public function shift_entry()
{
$args = func_get_args();
return call_user_func_array(array( $this, 'shiftEntry' ), $args);
}
/**
* Retrieve the next entry in the searchresult, but starting from last entry
*
* This is the opposite to {@link shiftEntry()} and is also very useful
* to be used inside a while loop.
*
* @return Net_LDAP2_Entry|false
*/
public function popEntry()
{
if (false === $this->_entry_cache) {
// fetch entries into cache if not done so far
$this->_entry_cache = $this->entries();
}
$return = array_pop($this->_entry_cache);
return (null === $return)? false : $return;
}
/**
* Alias function of popEntry() for perl-ldap interface
*
* @see popEntry()
* @return Net_LDAP2_Entry|false
*/
public function pop_entry()
{
$args = func_get_args();
return call_user_func_array(array( $this, 'popEntry' ), $args);
}
/**
* Return entries sorted as array
*
* This returns a array with sorted entries and the values.
* Sorting is done with PHPs {@link array_multisort()}.
* This method relies on {@link as_struct()} to fetch the raw data of the entries.
*
* Please note that attribute names are case sensitive!
*
* Usage example:
* <code>
* // to sort entries first by location, then by surename, but descending:
* $entries = $search->sorted_as_struct(array('locality','sn'), SORT_DESC);
* </code>
*
* @param array $attrs Array of attribute names to sort; order from left to right.
* @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
*
* @return array|Net_LDAP2_Error Array with sorted entries or error
* @todo what about server side sorting as specified in http://www.ietf.org/rfc/rfc2891.txt?
*/
public function sorted_as_struct($attrs = array('cn'), $order = SORT_ASC)
{
/*
* Old Code, suitable and fast for single valued sorting
* This code should be used if we know that single valued sorting is desired,
* but we need some method to get that knowledge...
*/
/*
$attrs = array_reverse($attrs);
foreach ($attrs as $attribute) {
if (!ldap_sort($this->_link, $this->_search, $attribute)){
$this->raiseError("Sorting failed for Attribute " . $attribute);
}
}
$results = ldap_get_entries($this->_link, $this->_search);
unset($results['count']); //for tidier output
if ($order) {
return array_reverse($results);
} else {
return $results;
}*/
/*
* New code: complete "client side" sorting
*/
// first some parameterchecks
if (!is_array($attrs)) {
return PEAR::raiseError("Sorting failed: Parameterlist must be an array!");
}
if ($order != SORT_ASC && $order != SORT_DESC) {
return PEAR::raiseError("Sorting failed: sorting direction not understood! (neither constant SORT_ASC nor SORT_DESC)");
}
// fetch the entries data
$entries = $this->as_struct();
// now sort each entries attribute values
// this is neccessary because later we can only sort by one value,
// so we need the highest or lowest attribute now, depending on the
// selected ordering for that specific attribute
foreach ($entries as $dn => $entry) {
foreach ($entry as $attr_name => $attr_values) {
sort($entries[$dn][$attr_name]);
if ($order == SORT_DESC) {
array_reverse($entries[$dn][$attr_name]);
}
}
}
// reformat entrys array for later use with array_multisort()
$to_sort = array(); // <- will be a numeric array similar to ldap_get_entries
foreach ($entries as $dn => $entry_attr) {
$row = array();
$row['dn'] = $dn;
foreach ($entry_attr as $attr_name => $attr_values) {
$row[$attr_name] = $attr_values;
}
$to_sort[] = $row;
}
// Build columns for array_multisort()
// each requested attribute is one row
$columns = array();
foreach ($attrs as $attr_name) {
foreach ($to_sort as $key => $row) {
$columns[$attr_name][$key] =& $to_sort[$key][$attr_name][0];
}
}
// sort the colums with array_multisort, if there is something
// to sort and if we have requested sort columns
if (!empty($to_sort) && !empty($columns)) {
$sort_params = '';
foreach ($attrs as $attr_name) {
$sort_params .= '$columns[\''.$attr_name.'\'], '.$order.', ';
}
eval("array_multisort($sort_params \$to_sort);"); // perform sorting
}
return $to_sort;
}
/**
* Return entries sorted as objects
*
* This returns a array with sorted Net_LDAP2_Entry objects.
* The sorting is actually done with {@link sorted_as_struct()}.
*
* Please note that attribute names are case sensitive!
* Also note, that it is (depending on server capabilitys) possible to let
* the server sort your results. This happens through search controls
* and is described in detail at {@link http://www.ietf.org/rfc/rfc2891.txt}
*
* Usage example:
* <code>
* // to sort entries first by location, then by surename, but descending:
* $entries = $search->sorted(array('locality','sn'), SORT_DESC);
* </code>
*
* @param array $attrs Array of sort attributes to sort; order from left to right.
* @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
*
* @return array|Net_LDAP2_Error Array with sorted Net_LDAP2_Entries or error
* @todo Entry object construction could be faster. Maybe we could use one of the factorys instead of fetching the entry again
*/
public function sorted($attrs = array('cn'), $order = SORT_ASC)
{
$return = array();
$sorted = $this->sorted_as_struct($attrs, $order);
if (PEAR::isError($sorted)) {
return $sorted;
}
foreach ($sorted as $key => $row) {
$entry = $this->_ldap->getEntry($row['dn'], $this->searchedAttrs());
if (!PEAR::isError($entry)) {
array_push($return, $entry);
} else {
return $entry;
}
}
return $return;
}
/**
* Return entries as array
*
* This method returns the entries and the selected attributes values as
* array.
* The first array level contains all found entries where the keys are the
* DNs of the entries. The second level arrays contian the entries attributes
* such that the keys is the lowercased name of the attribute and the values
* are stored in another indexed array. Note that the attribute values are stored
* in an array even if there is no or just one value.
*
* The array has the following structure:
* <code>
* $return = array(
* 'cn=foo,dc=example,dc=com' => array(
* 'sn' => array('foo'),
* 'multival' => array('val1', 'val2', 'valN')
* )
* 'cn=bar,dc=example,dc=com' => array(
* 'sn' => array('bar'),
* 'multival' => array('val1', 'valN')
* )
* )
* </code>
*
* @return array associative result array as described above
*/
public function as_struct()
{
$return = array();
$entries = $this->entries();
foreach ($entries as $entry) {
$attrs = array();
$entry_attributes = $entry->attributes();
foreach ($entry_attributes as $attr_name) {
$attr_values = $entry->getValue($attr_name, 'all');
if (!is_array($attr_values)) {
$attr_values = array($attr_values);
}
$attrs[$attr_name] = $attr_values;
}
$return[$entry->dn()] = $attrs;
}
return $return;
}
/**
* Set the search objects resource link
*
* @param resource $search Search result identifier
*
* @access public
* @return void
*/
public function setSearch($search)
{
$this->_search = $search;
}
/**
* Set the ldap ressource link
*
* @param resource $link Link identifier
*
* @access public
* @return void
*/
public function setLink($link)
{
$this->_link = $link;
}
/**
* Returns the number of entries in the searchresult
*
* @return int Number of entries in search.
*/
public function count()
{
// this catches the situation where OL returned errno 32 = no such object!
if (!$this->_search) {
return 0;
}
// ldap_count_entries is slow (see pear bug #18752) with large results,
// so we cache the result internally.
if ($this->_count_cache === null) {
$this->_count_cache = @ldap_count_entries($this->_link, $this->_search);
}
return $this->_count_cache;
}
/**
* Get the errorcode the object got in its search.
*
* @return int The ldap error number.
*/
public function getErrorCode()
{
return $this->_errorCode;
}
/**
* Destructor
*
* @access protected
*/
public function _Net_LDAP2_Search()
{
@ldap_free_result($this->_search);
}
/**
* Closes search result
*
* @return void
*/
public function done()
{
$this->_Net_LDAP2_Search();
}
/**
* Return the attribute names this search selected
*
* @return array
* @see $_searchedAttrs
* @access protected
*/
protected function searchedAttrs()
{
return $this->_searchedAttrs;
}
/**
* Tells if this search exceeds a sizelimit
*
* @return boolean
*/
public function sizeLimitExceeded()
{
return ($this->getErrorCode() == 4);
}
/*
* SPL Iterator interface methods.
* This interface allows to use Net_LDAP2_Search
* objects directly inside a foreach loop!
*/
/**
* SPL Iterator interface: Return the current element.
*
* The SPL Iterator interface allows you to fetch entries inside
* a foreach() loop: <code>foreach ($search as $dn => $entry) { ...</code>
*
* Of course, you may call {@link current()}, {@link key()}, {@link next()},
* {@link rewind()} and {@link valid()} yourself.
*
* If the search throwed an error, it returns false.
* False is also returned, if the end is reached
* In case no call to next() was made, we will issue one,
* thus returning the first entry.
*
* @return Net_LDAP2_Entry|false
*/
public function current()
{
if (count($this->_iteratorCache) == 0) {
$this->next();
reset($this->_iteratorCache);
}
$entry = current($this->_iteratorCache);
return ($entry instanceof Net_LDAP2_Entry)? $entry : false;
}
/**
* SPL Iterator interface: Return the identifying key (DN) of the current entry.
*
* @see current()
* @return string|false DN of the current entry; false in case no entry is returned by current()
*/
public function key()
{
$entry = $this->current();
return ($entry instanceof Net_LDAP2_Entry)? $entry->dn() :false;
}
/**
* SPL Iterator interface: Move forward to next entry.
*
* After a call to {@link next()}, {@link current()} will return
* the next entry in the result set.
*
* @see current()
* @return void
*/
public function next()
{
// fetch next entry.
// if we have no entrys anymore, we add false (which is
// returned by shiftEntry()) so current() will complain.
if (count($this->_iteratorCache) - 1 <= $this->count()) {
$this->_iteratorCache[] = $this->shiftEntry();
}
// move on array pointer to current element.
// even if we have added all entries, this will
// ensure proper operation in case we rewind()
next($this->_iteratorCache);
}
/**
* SPL Iterator interface: Check if there is a current element after calls to {@link rewind()} or {@link next()}.
*
* Used to check if we've iterated to the end of the collection.
*
* @see current()
* @return boolean FALSE if there's nothing more to iterate over
*/
public function valid()
{
return ($this->current() instanceof Net_LDAP2_Entry);
}
/**
* SPL Iterator interface: Rewind the Iterator to the first element.
*
* After rewinding, {@link current()} will return the first entry in the result set.
*
* @see current()
* @return void
*/
public function rewind()
{
reset($this->_iteratorCache);
}
}
?>
@@ -0,0 +1,97 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the example simple file based Schema Caching class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* A simple file based schema cacher with cache aging.
*
* Once the cache is too old, the loadSchema() method will return false, so
* Net_LDAP2 will fetch a fresh object from the LDAP server that will
* overwrite the current (outdated) old cache.
*/
class Net_LDAP2_SimpleFileSchemaCache implements Net_LDAP2_SchemaCache
{
/**
* Internal config of this cache
*
* @see Net_LDAP2_SimpleFileSchemaCache()
* @var array
*/
protected $config = array(
'path' => '/tmp/Net_LDAP_Schema.cache',
'max_age' => 1200
);
/**
* Initialize the simple cache
*
* Config is as following:
* path Complete path to the cache file.
* max_age Maximum age of cache in seconds, 0 means "endlessly".
*
* @param array $cfg Config array
*/
public function __construct($cfg)
{
foreach ($cfg as $key => $value) {
if (array_key_exists($key, $this->config)) {
if (gettype($this->config[$key]) != gettype($value)) {
$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key does not match type ".gettype($this->config[$key])."!");
}
$this->config[$key] = $value;
} else {
$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key is not defined!");
}
}
}
/**
* Return the schema object from the cache
*
* If file is existent and cache has not expired yet,
* then the cache is deserialized and returned.
*
* @return Net_LDAP2_Schema|Net_LDAP2_Error|false
*/
public function loadSchema()
{
$return = false; // Net_LDAP2 will load schema from LDAP
if (file_exists($this->config['path'])) {
$cache_maxage = filemtime($this->config['path']) + $this->config['max_age'];
if (time() <= $cache_maxage || $this->config['max_age'] == 0) {
$return = unserialize(file_get_contents($this->config['path']));
}
}
return $return;
}
/**
* Store a schema object in the cache
*
* This method will be called, if Net_LDAP2 has fetched a fresh
* schema object from LDAP and wants to init or refresh the cache.
*
* To invalidate the cache and cause Net_LDAP2 to refresh the cache,
* you can call this method with null or false as value.
* The next call to $ldap->schema() will then refresh the caches object.
*
* @param mixed $schema The object that should be cached
* @return true|Net_LDAP2_Error|false
*/
public function storeSchema($schema) {
file_put_contents($this->config['path'], serialize($schema));
return true;
}
}
+620
View File
@@ -0,0 +1,620 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Util interface class.
*
* PHP version 5
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id$
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Utility Class for Net_LDAP2
*
* This class servers some functionality to the other classes of Net_LDAP2 but most of
* the methods can be used separately as well.
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Util extends PEAR
{
/**
* Constructor
*
* @access public
*/
public function __construct()
{
// We do nothing here, since all methods can be called statically.
// In Net_LDAP <= 0.7, we needed a instance of Util, because
// it was possible to do utf8 encoding and decoding, but this
// has been moved to the LDAP class. The constructor remains only
// here to document the downward compatibility of creating an instance.
}
/**
* Explodes the given DN into its elements
*
* {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
* of Relative Distinguished Names (RDNs), which themselves
* are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
*
* For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
* <kbd>array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )</kbd>
*
* [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
* the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
* and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
* the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
* [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
* See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
*
* It also performs the following operations on the given DN:
* - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
* and strings beginning with "#".
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
*
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute types names.
* Attribute values are not affected by this option.
* The default is to uppercase. Valid values are:
* lower Lowercase attribute types names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* reverse If TRUE, the RDN sequence is reversed.
* onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
*
* @param string $dn The DN that should be exploded
* @param array $options Options to use
*
* @static
* @return array Parts of the exploded DN
* @todo implement BER
*/
public static function ldap_explode_dn($dn, $options = array('casefold' => 'upper'))
{
if (!isset($options['onlyvalues'])) $options['onlyvalues'] = false;
if (!isset($options['reverse'])) $options['reverse'] = false;
if (!isset($options['casefold'])) $options['casefold'] = 'upper';
// Escaping of DN and stripping of "OID."
$dn = self::canonical_dn($dn, array('casefold' => $options['casefold']));
// splitting the DN
$dn_array = preg_split('/(?<=[^\\\\]),/', $dn);
// clear wrong splitting (possibly we have split too much)
// /!\ Not clear, if this is neccessary here
//$dn_array = self::correct_dn_splitting($dn_array, ',');
// construct subarrays for multivalued RDNs and unescape DN value
// also convert to output format and apply casefolding
foreach ($dn_array as $key => $value) {
$value_u = self::unescape_dn_value($value);
$rdns = self::split_rdn_multival($value_u[0]);
if (count($rdns) > 1) {
// MV RDN!
foreach ($rdns as $subrdn_k => $subrdn_v) {
// Casefolding
if ($options['casefold'] == 'upper') {
$subrdn_v = preg_replace_callback(
"/^\w+=/",
function ($matches) {
return strtoupper($matches[0]);
},
$subrdn_v
);
} else if ($options['casefold'] == 'lower') {
$subrdn_v = preg_replace_callback(
"/^\w+=/",
function ($matches) {
return strtolower($matches[0]);
},
$subrdn_v
);
}
if ($options['onlyvalues']) {
preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches);
$rdn_ocl = $matches[1];
$rdn_val = $matches[2];
$unescaped = self::unescape_dn_value($rdn_val);
$rdns[$subrdn_k] = $unescaped[0];
} else {
$unescaped = self::unescape_dn_value($subrdn_v);
$rdns[$subrdn_k] = $unescaped[0];
}
}
$dn_array[$key] = $rdns;
} else {
// normal RDN
// Casefolding
if ($options['casefold'] == 'upper') {
$value = preg_replace_callback(
"/^\w+=/",
function ($matches) {
return strtoupper($matches[0]);
},
$value
);
} else if ($options['casefold'] == 'lower') {
$value = preg_replace_callback(
"/^\w+=/",
function ($matches) {
return strtolower($matches[0]);
},
$value
);
}
if ($options['onlyvalues']) {
preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches);
$dn_ocl = $matches[1];
$dn_val = $matches[2];
$unescaped = self::unescape_dn_value($dn_val);
$dn_array[$key] = $unescaped[0];
} else {
$unescaped = self::unescape_dn_value($value);
$dn_array[$key] = $unescaped[0];
}
}
}
if ($options['reverse']) {
return array_reverse($dn_array);
} else {
return $dn_array;
}
}
/**
* Escapes a DN value according to RFC 2253
*
* Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
* The characters ",", "+", """, "\", "<", ">", ";", "#", "=" with a special meaning in RFC 2252
* are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
* Finally all leading and trailing spaces are converted to sequences of \20.
*
* @param array $values An array containing the DN values that should be escaped
*
* @static
* @return array The array $values, but escaped
*/
public static function escape_dn_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// Escaping of filter meta characters
$val = str_replace('\\', '\\\\', $val);
$val = str_replace(',', '\,', $val);
$val = str_replace('+', '\+', $val);
$val = str_replace('"', '\"', $val);
$val = str_replace('<', '\<', $val);
$val = str_replace('>', '\>', $val);
$val = str_replace(';', '\;', $val);
$val = str_replace('#', '\#', $val);
$val = str_replace('=', '\=', $val);
// ASCII < 32 escaping
$val = self::asc2hex32($val);
// Convert all leading and trailing spaces to sequences of \20.
if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
$val = $matches[2];
for ($i = 0; $i < strlen($matches[1]); $i++) {
$val = '\20'.$val;
}
for ($i = 0; $i < strlen($matches[3]); $i++) {
$val = $val.'\20';
}
}
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
$values[$key] = $val;
}
return $values;
}
/**
* Undoes the conversion done by escape_dn_value().
*
* Any escape sequence starting with a baskslash - hexpair or special character -
* will be transformed back to the corresponding character.
*
* @param array $values Array of DN Values
*
* @return array Same as $values, but unescaped
* @static
*/
public static function unescape_dn_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// strip slashes from special chars
$val = str_replace('\\\\', '\\', $val);
$val = str_replace('\,', ',', $val);
$val = str_replace('\+', '+', $val);
$val = str_replace('\"', '"', $val);
$val = str_replace('\<', '<', $val);
$val = str_replace('\>', '>', $val);
$val = str_replace('\;', ';', $val);
$val = str_replace('\#', '#', $val);
$val = str_replace('\=', '=', $val);
// Translate hex code into ascii
$values[$key] = self::hex2asc($val);
}
return $values;
}
/**
* Returns the given DN in a canonical form
*
* Returns false if DN is not a valid Distinguished Name.
* DN can either be a string or an array
* as returned by ldap_explode_dn, which is useful when constructing a DN.
* The DN array may have be indexed (each array value is a OCL=VALUE pair)
* or associative (array key is OCL and value is VALUE).
*
* It performs the following operations on the given DN:
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
* - Converts all leading and trailing spaces in values to be \20.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
*
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute type names.
* Attribute values are not affected by this option. The default is to uppercase.
* Valid values are:
* lower Lowercase attribute type names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
* reverse If TRUE, the RDN sequence is reversed.
* separator Separator to use between RDNs. Defaults to comma (',').
*
* Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
* because an empty string evaluates to false. Use the "===" operator instead.
*
* @param array|string $dn The DN
* @param array $options Options to use
*
* @static
* @return false|string The canonical DN or FALSE
* @todo implement option mbcescape
*/
public static function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ','))
{
if ($dn === '') return $dn; // empty DN is valid!
// options check
if (!isset($options['reverse'])) {
$options['reverse'] = false;
} else {
$options['reverse'] = true;
}
if (!isset($options['casefold'])) $options['casefold'] = 'upper';
if (!isset($options['separator'])) $options['separator'] = ',';
if (!is_array($dn)) {
// It is not clear to me if the perl implementation splits by the user defined
// separator or if it just uses this separator to construct the new DN
$dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn);
// clear wrong splitting (possibly we have split too much)
$dn = self::correct_dn_splitting($dn, $options['separator']);
} else {
// Is array, check, if the array is indexed or associative
$assoc = false;
foreach ($dn as $dn_key => $dn_part) {
if (!is_int($dn_key)) {
$assoc = true;
}
}
// convert to indexed, if associative array detected
if ($assoc) {
$newdn = array();
foreach ($dn as $dn_key => $dn_part) {
if (is_array($dn_part)) {
ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative
$newdn[] = $dn_part; // copy array as-is, so we can resolve it later
} else {
$newdn[] = $dn_key.'='.$dn_part;
}
}
$dn =& $newdn;
}
}
// Escaping and casefolding
foreach ($dn as $pos => $dnval) {
if (is_array($dnval)) {
// subarray detected, this means very surely, that we had
// a multivalued dn part, which must be resolved
$dnval_new = '';
foreach ($dnval as $subkey => $subval) {
// build RDN part
if (!is_int($subkey)) {
$subval = $subkey.'='.$subval;
}
$subval_processed = self::canonical_dn($subval);
if (false === $subval_processed) return false;
$dnval_new .= $subval_processed.'+';
}
$dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus
} else {
// try to split multivalued RDNS into array
$rdns = self::split_rdn_multival($dnval);
if (count($rdns) > 1) {
// Multivalued RDN was detected!
// The RDN value is expected to be correctly split by split_rdn_multival().
// It's time to sort the RDN and build the DN!
$rdn_string = '';
sort($rdns, SORT_STRING); // Sort RDN keys alphabetically
foreach ($rdns as $rdn) {
$subval_processed = self::canonical_dn($rdn);
if (false === $subval_processed) return false;
$rdn_string .= $subval_processed.'+';
}
$dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus
} else {
// no multivalued RDN!
// split at first unescaped "="
$dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2);
$ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
$val = $dn_comp[1];
// strip 'OID.', otherwise apply casefolding and escaping
if (substr(strtolower($ocl), 0, 4) == 'oid.') {
$ocl = substr($ocl, 4);
} else {
if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl);
if ($options['casefold'] == 'lower') $ocl = strtolower($ocl);
$ocl = self::escape_dn_value(array($ocl));
$ocl = $ocl[0];
}
// escaping of dn-value
$val = self::escape_dn_value(array($val));
$val = str_replace('/', '\\2f', $val[0]);
$dn[$pos] = $ocl.'='.$val;
}
}
}
if ($options['reverse']) $dn = array_reverse($dn);
return implode($options['separator'], $dn);
}
/**
* Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
*
* Any control characters with an ACII code < 32 as well as the characters with special meaning in
* LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
* backslash followed by two hex digits representing the hexadecimal value of the character.
*
* @param array $values Array of values to escape
*
* @static
* @return array Array $values, but escaped
*/
public static function escape_filter_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// Escaping of filter meta characters
$val = str_replace('\\', '\5c', $val);
$val = str_replace('*', '\2a', $val);
$val = str_replace('(', '\28', $val);
$val = str_replace(')', '\29', $val);
// ASCII < 32 escaping
$val = self::asc2hex32($val);
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
$values[$key] = $val;
}
return $values;
}
/**
* Undoes the conversion done by {@link escape_filter_value()}.
*
* Converts any sequences of a backslash followed by two hex digits into the corresponding character.
*
* @param array $values Array of values to escape
*
* @static
* @return array Array $values, but unescaped
*/
public static function unescape_filter_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $value) {
// Translate hex code into ascii
$values[$key] = self::hex2asc($value);
}
return $values;
}
/**
* Converts all ASCII chars < 32 to "\HEX"
*
* @param string $string String to convert
*
* @static
* @return string
*/
public static function asc2hex32($string)
{
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
if (ord($char) < 32) {
$hex = dechex(ord($char));
if (strlen($hex) == 1) $hex = '0'.$hex;
$string = str_replace($char, '\\'.$hex, $string);
}
}
return $string;
}
/**
* Converts all Hex expressions ("\HEX") to their original ASCII characters
*
* @param string $string String to convert
*
* @static
* @author beni@php.net, heavily based on work from DavidSmith@byu.net
* @return string
*/
public static function hex2asc($string)
{
$string = preg_replace_callback(
"/\\\[0-9A-Fa-f]{2}/",
function ($matches) {
return chr(hexdec($matches[0]));
},
$string
);
return $string;
}
/**
* Split an multivalued RDN value into an Array
*
* A RDN can contain multiple values, spearated by a plus sign.
* This function returns each separate ocl=value pair of the RDN part.
*
* If no multivalued RDN is detected, an array containing only
* the original rdn part is returned.
*
* For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
* <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd>
*
* The method trys to be smart if it encounters unescaped "+" characters, but may fail,
* so ensure escaped "+"es in attr names and attr values.
*
* [BUG] If you have a multivalued RDN with unescaped plus characters
* and there is a unescaped plus sign at the end of an value followed by an
* attribute name containing an unescaped plus, then you will get wrong splitting:
* $rdn = 'OU=Sales+C+N=J. Smith';
* returns:
* array('OU=Sales+C', 'N=J. Smith');
* The "C+" is treaten as value of the first pair instead as attr name of the second pair.
* To prevent this, escape correctly.
*
* @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
*
* @static
* @return array Array with the components of the multivalued RDN or Error
*/
public static function split_rdn_multival($rdn)
{
$rdns = preg_split('/(?<!\\\\)\+/', $rdn);
$rdns = self::correct_dn_splitting($rdns, '+');
return array_values($rdns);
}
/**
* Splits an attribute=value syntax into an array
*
* If escaped delimeters are used, they are returned escaped as well.
* The split will occur at the first unescaped delimeter character.
* In case an invalid delimeter is given, no split will be performed and an
* one element array gets returned.
* Optional also filter-assertion delimeters can be considered (>, <, >=, <=, ~=).
*
* @param string $attr Attribute and Value Syntax ("foo=bar")
* @param boolean $extended If set to true, also filter-assertion delimeter will be matched
* @param boolean $withDelim If set to true, the return array contains the delimeter at index 1, putting the value to index 2
*
* @return array Indexed array: 0=attribute name, 1=attribute value OR ($withDelim=true): 0=attr, 1=delimeter, 2=value
*/
public static function split_attribute_string($attr, $extended=false, $withDelim=false)
{
if ($withDelim) $withDelim = PREG_SPLIT_DELIM_CAPTURE;
if (!$extended) {
return preg_split('/(?<!\\\\)(=)/', $attr, 2, $withDelim);
} else {
return preg_split('/(?<!\\\\)(>=|<=|>|<|~=|=)/', $attr, 2, $withDelim);
}
}
/**
* Corrects splitting of dn parts
*
* @param array $dn Raw DN array
* @param array $separator Separator that was used when splitting
*
* @return array Corrected array
* @access protected
*/
protected static function correct_dn_splitting($dn = array(), $separator = ',')
{
foreach ($dn as $key => $dn_value) {
$dn_value = $dn[$key]; // refresh value (foreach caches!)
// if the dn_value is not in attr=value format, then we had an
// unescaped separator character inside the attr name or the value.
// We assume, that it was the attribute value.
// [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
// must remain independent from the other classes or connections.
if (!preg_match('/.+(?<!\\\\)=.+/', $dn_value)) {
unset($dn[$key]);
if (array_key_exists($key-1, $dn)) {
$dn[$key-1] = $dn[$key-1].$separator.$dn_value; // append to previous attr value
} else {
$dn[$key+1] = $dn_value.$separator.$dn[$key+1]; // first element: prepend to next attr name
}
}
}
return array_values($dn);
}
}
?>
+25
View File
@@ -0,0 +1,25 @@
{
"name": "pear/net_ldap2",
"description": "Object oriented interface for searching and manipulating LDAP-entries",
"license": "LGPL-3.0",
"type": "library",
"keywords": [
"pear",
"ldap"
],
"homepage": "http://pear.php.net/package/Net_LDAP2",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_LDAP2",
"source": "https://github.com/pear/Net_LDAP2"
},
"require": {
"pear/pear-core-minimal": "^1.10.1",
"ext-ldap": "*"
},
"include-path": ["."],
"autoload": {
"classmap": [
"Net/"
]
}
}
+877
View File
@@ -0,0 +1,877 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.11" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>Net_LDAP2</name>
<channel>pear.php.net</channel>
<extends>Net_LDAP</extends>
<summary>Object oriented interface for searching and manipulating LDAP-entries</summary>
<description>Net_LDAP2 is the successor of Net_LDAP which is a clone of Perls Net::LDAP
object interface to directory servers. It does contain most of Net::LDAPs
features but has some own too.
With Net_LDAP2 you have:
* A simple object-oriented interface to connections, searches entries and filters.
* Support for TLS and LDAP v3.
* Simple modification, deletion and creation of LDAP entries.
* Support for schema handling.
Net_LDAP2 layers itself on top of PHP's existing ldap extensions.
</description>
<lead>
<name>Benedikt Hallinger</name>
<user>beni</user>
<email>beni@php.net</email>
<active>yes</active>
</lead>
<!-- Information for this release -->
<date>2023-02-23</date>
<version>
<release>2.2.1</release>
<api>2.2.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Fix: remove unused $ber property
</notes>
<contents>
<dir name="/">
<dir name="Net">
<file name="LDAP2.php" role="php" />
<dir name="LDAP2">
<file name="Entry.php" role="php" />
<file name="Filter.php" role="php" />
<file name="RootDSE.php" role="php" />
<file name="Schema.php" role="php" />
<file name="Search.php" role="php" />
<file name="Util.php" role="php" />
<file name="LDIF.php" role="php" />
<file name="SchemaCache.interface.php" role="php" />
<file name="SimpleFileSchemaCache.php" role="php" />
</dir> <!-- /LDAP2 -->
</dir> <!-- /Net -->
<dir name="doc">
<file name="manual.html" role="doc" />
<file name="README.txt" role="doc" />
<file name="RootDSE.txt" role="doc" />
<file name="Schema.txt" role="doc" />
<file name="utf8.txt" role="doc" />
<file name="examples/connecting.php" role="doc" />
<file name="examples/fetch_entry.php" role="doc" />
<file name="examples/search_entries.php" role="doc" />
<file name="examples/add_entry.php" role="doc" />
<file name="examples/modify_entry.php" role="doc" />
<file name="examples/modify_entry2.php" role="doc" />
<file name="examples/schema_cache.php" role="doc" />
</dir> <!-- /doc -->
<dir name="tests">
<file name="phpunit.xml" role="test" />
<file name="Net_LDAP2_TestBase.php" role="test" />
<file name="Net_LDAP2_EntryTest.php" role="test" />
<file name="Net_LDAP2_FilterTest.php" role="test" />
<file name="Net_LDAP2_RootDSETest.php" role="test" />
<file name="Net_LDAP2_SearchTest.php" role="test" />
<file name="Net_LDAP2Test.php" role="test" />
<file name="Net_LDAP2_UtilTest.php" role="test" />
<file name="Net_LDAP2_LDIFTest.php" role="test" />
<file name="ldapconfig.ini.dist" role="test" />
<file name="ldapldifconfig.ini.dist" role="test" />
<file name="ldif_data/base.ldif" role="test" />
<file name="ldif_data/malformed_syntax.ldif" role="test" />
<file name="ldif_data/malformed_syntax.ldif" role="test" />
<file name="ldif_data/malformed_wrapping.ldif" role="test" />
<file name="ldif_data/slapd.conf" role="test" />
<file name="ldif_data/sorted_w40.ldif" role="test" />
<file name="ldif_data/sorted_w50.ldif" role="test" />
<file name="ldif_data/unsorted_w30.ldif" role="test" />
<file name="ldif_data/unsorted_w50.ldif" role="test" />
<file name="ldif_data/unsorted_w50_WIN.ldif" role="test" />
<file name="ldif_data/changes.ldif" role="test" />
</dir> <!-- /tests -->
</dir> <!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>7.4</min>
</php>
<pearinstaller>
<min>1.10.1</min>
</pearinstaller>
<extension>
<name>ldap</name>
</extension>
</required>
</dependencies>
<phprelease />
<!-- CHANGELOG -->
<changelog>
<release>
<version>
<release>0.1</release>
<api>0.1</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-06-23</date>
<license>LGPL License</license>
<notes>Initial release
</notes>
</release>
<release>
<version>
<release>0.2</release>
<api>0.2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-08-23</date>
<license>LGPL License</license>
<notes>Fixed a lot of bugs that jumped in during the pearification process
</notes>
</release>
<release>
<version>
<release>0.3</release>
<api>0.3</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-09-21</date>
<license>LGPL License</license>
<notes>More bug squashing! Much better errorhandling in the -&gt;search() function.
Also, all errors that create a Pear_error now includes the errornumber if
appropriate (i.e. it was an ldap generated error).
</notes>
</release>
<release>
<version>
<release>0.4</release>
<api>0.4</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-10-01</date>
<license>LGPL License</license>
<notes>Many more bugfixes. Jan Wagner fixed the shift_entry function.
Also a new Net_LDAP_Entry::modify function has been added that goes far making a simple way to modify entries.
</notes>
</release>
<release>
<version>
<release>0.5</release>
<api>0.5</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-10-11</date>
<license>LGPL License</license>
<notes>Jan Wagner Contributed a new RootDSE object and a Schema object and some fixes to the Net_LDAP::search() method
The new Net_ldap_entry::modify() method seems to work very nice now.
</notes>
</release>
<release>
<version>
<release>0.6</release>
<api>0.6</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-10-17</date>
<license>LGPL License</license>
<notes>New Net_LDAP::ArrayUTF8Decode and Net_LDAP::ArrayUTF8Encode functions. These are used by the Net_LDAP::Entry objects to ensure that things work ok.
</notes>
</release>
<release>
<version>
<release>0.6.3</release>
<api>0.6.3</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2003-11-12</date>
<license>LGPL License</license>
<notes>It seems that 0.6.2 was out too fast. So this is mainly a bugfix release:
- Removed remaining Net_LDAP::UTF8Encode and Net_LDAP::UTF8Decode calls in Net_LDAP_Entry,
which stopped attributes() and get_entry() from working
- The UTF8 functions somehow got outside the Net_LDAP class ... FIXED.
- The usuage example of the last release was wrong. We decided to move UTF8 handling into Net_LDAP.
Handling should be done this way:
$attr = $ldap-&gt;utf8Encode($attr);
$entry-&gt;modify($attr);
$attr = $ldap-&gt;utf8Decode( $entry-&gt;attributes() );
- This means Net_LDAP_Util is useless right now, but will be extended in the future.
- Jan did a complete overhaul of the phpdoc stuff. Everything seems to be fine now with phpDocumentor.
</notes>
</release>
<release>
<date>2007-02-05</date>
<version>
<release>0.7.0</release>
<api>0.7.0</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>This long awaited release of Net_LDAP features more stability and new functionality.
The main changes are:
- Rewrite of much of the code (including some api changes!)
- LOTS of fixed bugs!
- New class for easy filter handling (Net_LDAP_Filter)
- Sorting support for searchresults (including multivalued sorting!)
- Searched Entries can now be fetched as_struct() (array)!
- Some memory optimizations
Please note also that Net_LDAPs configuration changed slightly. Please see $_config in LDAP.php for the new parameters.
</notes>
</release>
<release>
<date>2007-02-23</date>
<version>
<release>0.7.1</release>
<api>0.7.0</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>This is not just a bugfix release of 0.7.0 but also introduces some internal optimisations:
- Fixed a connection bug whith LDAP V3 only servers
- clearer sanitizing of the host config parameter
</notes>
</release>
<release>
<date>2007-05-07</date>
<version>
<release>0.7.2</release>
<api>0.7.2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>This release features some internal code movements to be more compatible to PERL::Net_LDAP.
The movements include:
* Removed UTF8 en-/decoding stuff from Net_LDAP_Utils class since this was moved to Net_LDAP class in 0.6.6
* Moved Filter encoding from Net_LDAP_Filter to Net_LDAP_Util
* Moved ldap_explode_dn_escaped() from Net_LDAP_Entry to Net_LDAP_Util
* Added perls functions from Net_LDAP::Util to our Util class, but they still need some work
Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Entry anymore.
Additionally some new functionality has been introduced:
* You can now apply regular expressions directly to a entrys attributes
and don't need to fetch the attribute values manually.
* Net_LDAP_Schema can check if a attributes syntax is binary
The following bugs have been resolved:
* Connections to LDAP servers that forbid anonymous binds are possible again
* The JPEG attribute is now properly returned as binary value instead of string
* If the array describing selected attributes in searches didn't contain consecutive keys, there was a problem sometimes
* Some PHP5 return issues
</notes>
</release>
<release>
<date>2007-06-12</date>
<version>
<release>0.7.3</release>
<api>0.7.2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>This release introduces some example files showing you in detail how to work with Net_LDAP.
Additionally, a bug at recursive deletion of an entry is fixed and the Net_LDAP_Filter
class is slightly optimized.
</notes>
</release>
<release>
<date>2007-06-20</date>
<version>
<release>1.0.0RC1</release>
<api>1.0.0RC1</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>Again some small Bugfixes, most notably a bug within $ldap->modify() that occured when using the
combined 'changes' array.
Besides that, $search->popEntry() and the corresponding alias pop_entry() has been implemented.
Net_LDAP_Util::unescape_filter_value() is available too now and Net_LDAP_Util::escape_filter_value()
can handle ASCII chars smaller than 32. Above that, Net_LDAP_Util::canonical_dn() has been fully implemented.
A new method createFresh() was added to Net_LDAP_Entry, so creation of initial entries is more
standardized and clearer.
A new example is available, describing the $ldap->modify() method.
The add_entry.php example was updated, it shows the use of Net_LDAP_Entry::createFresh().
$ldap->add() links unlinked entries now to the connection used for the add.
Some new additional utility functions are available in Net_LDAP_Util to assist you in handling attributes and dns.
The LDAP-Rename command now uses this functions to deal with DN escaping issues.
Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Util anymore; it got superseeded by Net_LDAP_Util::ldap_explode_dn().
</notes>
</release>
<release>
<date>2007-06-28</date>
<version>
<release>1.0.0RC2</release>
<api>1.0.0RC2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>Net_LDAP->dnExists() uses the Util class now, which makes it safer.
A new move() method is available from Net_LDAP.
Please note, that the copy() method was removed from the Net_LDAP_Entry class since
people would expect attribute moving because of the overall API of Net_LDAP.
Instead use the more failsafer copy() from Net_LDAP.
</notes>
</release>
<release>
<date>2007-07-24</date>
<version>
<release>1.0.0RC3</release>
<api>1.0.0RC3</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
Fixed a bug with dnExists() that was caused mainly by bad behavior of Net_LDAP_UTIL::ldap_explode_dn().
Fixed a bug with call time pass-by-reference if calling $entry->update(); however this inflicted a API change:
The parameter $ldap is not available anymore, you need to use $entry->setLDAP() prior update now if you want to change the LDAP
object. This brought us a more logical API now, since Entry operations should be performed by the Net_LDAP object.
</notes>
</release>
<release>
<date>2007-09-18</date>
<version>
<release>1.0.0RC4</release>
<api>1.0.0RC4</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
- Fixed some minor bugs of RC3
- Reintroduced $ldap parameter for
$entry-&lt;update(), but it is not prefferred to use this way.
The Parameter is there for perl interface compatibility
</notes>
</release>
<release>
<date>2007-10-29</date>
<version>
<release>1.0.0</release>
<api>1.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
After more than four years of development, we are very proud to announce the
~ FIRST STABLE Net_LDAP RELEASE 1.0.0 ~
Net_LDAP ist tested now and should be stable enough for production use.
The API is finished so far, no changes should be neccessary in the future.
Changes to Release candidate 4:
- Implemented PHPUnit tests
- Fixed some minor bugs of RC4 (including the schema loading warning-generation)
- Fixed several bugs in Net_LDAP_Util
- Improved Net_LDAP_Filter and Net_LDAP_Util error handling and code cleanness
- Completely implemented Net_LDAP_Filter perl interface
- Improved several doc comments and fixed some spelling errors
</notes>
</release>
<release>
<date>2008-01-14</date>
<version>
<release>1.1.0a1</release>
<api>1.1.0a1</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Added LDIF reading and writing support
* Fixed minor issues of 1.0.0 release
</notes>
</release>
<release>
<date>2008-01-21</date>
<version>
<release>1.1.0a2</release>
<api>1.1.0a2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Added parseLines() to Net_LDAP_LDIF for more convinience
* Added some handy methods to Net_LDAP_Entry
* Enhanced tests
</notes>
</release>
<release>
<date>2008-02-27</date>
<version>
<release>1.1.0</release>
<api>1.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>* Fixed a little bug at cross directory move
* Fixed a bug when deleting a subtree containing several subentries that failed if
one called dnExists() prior calling delete()
* Fixed some minor bugs at NeT_LDAP->move() and Net_LDAP->dnExists()
* Added Net_LDAP tests
* Changed API of Net_LDAP->copy() to only accept Net_LDAP_Entry objects, because with DNs
Attribute values will be lost
/!\ This is the last release of Net_LDAP supporting PHP4 /!\
</notes>
</release>
<release>
<date>2008-03-19</date>
<version>
<release>2.0.0RC1</release>
<api>2.0.0RC1</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>/!\ This release is PHP5 only, replacing the Net_LDAP package.
If you still need PHP4 support, use Net_LDAP instead.
* Implemented iterable search results so one can use foreach() with Net_LDAP2_Search objects
* Fixed a problem with Net_LDAP2_LDIF and files with DOS line endings
</notes>
</release>
<release>
<date>2008-03-20</date>
<version>
<release>2.0.0RC2</release>
<api>2.0.0RC2</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Implemented PHP5 language stuff (thanks to Torsten Roehr for his helpful patches)
* Changed Net_LDAP2-&gt;_markAsNew() to public access, since this is required by the api
and may be useful to developers too
* Changed API to create schema object, there is now a factory. Net_LDAP2-&gt;schema() calls
that factory now instead of fetching the Schema itself
* Changed API to create rootDSE object, there is now a factory. Net_LDAP2-&gt;rootDSE() calls
that factory now instead of fetching the rootDSE itself
* Net_LDAP2_Entry has a new factory constructor: createConnected() can be used to
establish a new Net_LDAP2_Entry object that represents an already existing entry inside
some directory
</notes>
</release>
<release>
<date>2008-06-04</date>
<version>
<release>2.0.0RC3</release>
<api>2.0.0RC3</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* New constructor factory for Entry objects: createExisting()
* Several small improvements
* New method Entry-&gt;isNew()
* Net_LDAP2-&gt;search() and Net_LDAP2-&gt;dnExists() can handle entry objects now
* Added &quot;present&quot; matching rule as stated by RFC 2254 (is an alias of the former &quot;any&quot;)
* Bugfix in filter class for approx matching and not combination
* Bugfix for Schema-&gt;isBinary() bug if unknown attribute type is requested
</notes>
</release>
<release>
<date>2008-10-16</date>
<version>
<release>2.0.0RC4</release>
<api>2.0.0RC4</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Net_LDAP2_Filter::create*() methods are declared static now
* Net_LDAP2 is able to reconnect now in case link is down during operation (thanks Del)
* Complex updates fail: under some circumstances, $entry-&gt;update() will fail. This is
caused by mandatory attributes set and the internal behaviour of Net_LDAP2. A
workaround is documented in the code and in the user manual.
* Bugfixes in Dels patches. Soemtimes there where endless loops and deleting entries
did not always succeed.
* Bugfix to reset unicodePwd (Active Directory): a new $force parameter was introduced to Net_LDAP2_Entry-&gt;replace()
that forces &quot;replace&quot; mode. If not set and attribute is empty (or could not be read like in the AD case)
replace() resulted in Net_LDAP2 thinking it should add the attribute. This can now be overriden.
* Bugfix in unit tests: the Net_LDAP2Test suite had huge memory consumtion caused by a little error in
Net_LDAP2-&gt;checkLDAPExtension(). If that method is called before any Net_LDAP class was instanciated, a PEAR
error is returned instead of the documented Net_LDAP2_Error which causes the unit test to plot out very much
debug information.
</notes>
</release>
<release>
<date>2009-01-09</date>
<version>
<release>2.0.0RC5</release>
<api>2.0.0RC5</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed a little issue with repetive adding the same attribute value
* Fixed Bug #14886 that caused problems with OpenLDAP and V3 only connects
* Fixed Bug #14903 and #15494, now bind attempt is also encrypted if TLS is requested
* Fixed issue with repetitve adding or deleting values causing Net_LDAP to send the same change multiple times
* Fixed Bug #15364 that caused a problem with setting the ldap version if only one version is supported by server
</notes>
</release>
<release>
<date>2009-05-08</date>
<version>
<release>2.0.0RC6</release>
<api>2.0.0RC6</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license>LGPL License</license>
<notes>
* Bugfix in LDIF writer concerning needless base64 encoding of values
* New schema caching facility
* PHPCS fixes, some comment changes and general code cleanup
</notes>
</release>
<release>
<date>2009-05-28</date>
<version>
<release>2.0.0</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed bug #16242 (arguments for createFresh in wrong order when calling Net_LDAP2_Entry::createFresh())
* Fixed bug #16253 (strict checking of isError())
</notes>
</release>
<release>
<date>2009-06-15</date>
<version>
<release>2.0.1</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed Bugs #16272 and #16278 (Problem in starttls function)
</notes>
</release>
<release>
<date>2009-06-29</date>
<version>
<release>2.0.2</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed some bugs that rendered the new schema caching facility unusable
</notes>
</release>
<release>
<date>2009-07-03</date>
<version>
<release>2.0.3</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed LDAP RFC-1777 violation: bind has to be performed prior setting LDAP version
* Fixed wrong version reported from version()
</notes>
</release>
<release>
<date>2009-07-08</date>
<version>
<release>2.0.4</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed Bug #16404 (Bind fails at OpenLDAP with protocol error)
</notes>
</release>
<release>
<date>2009-07-14</date>
<version>
<release>2.0.5</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPL License</license>
<notes>
* Fixed bug #16438 (SimplefileSchemaCache could not be configured due to wrong var name)
</notes>
</release>
<release>
<date>2009-08-04</date>
<version>
<release>2.0.6</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* switched LICENSE to LGPL v3
* added some documentation
</notes>
</release>
<release>
<date>2009-10-28</date>
<version>
<release>2.0.7</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Corrected bug #16738 (Problem with Net_LDAP2_Filter::parse() with complex filter, when first subfilter was an combined filter too)
</notes>
</release>
<release>
<version>
<release>2.0.8</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<date>2010-02-12</date>
<license>LGPLv3 License</license>
<notes>
* Fixed Bug #16940 (Net_LDAP2::startTLS should ignore errors before ldap_start_tls() being called)
* Fixed Bug #17023 (improper handling of wrapped lines in LDIF files)
</notes>
</release>
<release>
<date>2010-02-16</date>
<version>
<release>2.0.8</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Fixed Bug #16940 (Net_LDAP2::startTLS should ignore errors before ldap_start_tls() being called)
* Fixed Bug #17023 (improper handling of wrapped lines in LDIF files)
* Fixed Bug #17057 (problem with parsing certain NOT-Filters)
</notes>
</release>
<release>
<date>2010-02-16</date>
<version>
<release>2.0.9</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* fixed package (package.xml was unclean so 2.0.8 wouldnt install)
</notes>
</release>
<release>
<date>2010-08-23</date>
<version>
<release>2.0.10</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Added schema handling methods to make schema checks more easily accessible
* Bugfix for #17245. The check in the code was not working properly. Schema checking is considered the users responsibility.
If now an attribute is requested that is not set at the entry, an empty string is returned.
* Bugfix for #17770. Some Net_LDAP2 files were included with relative path ("Util.php"), not absolute ("Net/LDAP2/Util.php").
* Bugfix for #17314. LDIF support for attributes with modifiers ("attr1;binary").
</notes>
</release>
<release>
<date>2011-01-19</date>
<version>
<release>2.0.11</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* (doc issue) Fix for #17861: Missing komma in example
* Fix for #18202: Adding attributes to a Fresh Entry saving and laterly updating fails
</notes>
</release>
<release>
<date>2011-10-27</date>
<version>
<release>2.0.12</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* inmproved performance with large search results
* Fixed some minor issues with Net_LDAP2_Filter and Net_LDAP2->dnExists()
* Added NOT filter to Net_LDAP2_Filter::create() so negating is more easily now
</notes>
</release>
<release>
<date>2013-12-09</date>
<version>
<release>2.1.0</release>
<api>2.0.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* New feature: Filter::matches() can do simple filtering on entry sets (supported: equals, contain, begin, end, any; NOT, AND, OR. Filtering is simple based on regexp, no schema checks and matchRules yet!)
* Fixed minor bugs in Filter, LDAP and Entry class
* Util::split_attribute_string(): Added support for extended match operators from filters
* Util::split_attribute_string(): Added support for delimeter retrieval
</notes>
</release>
<release>
<date>2015-10-30</date>
<version>
<release>2.2.0</release>
<api>2.2.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Fix bug #20969: Fatal error with PEAR 1.10.0 / constructor visiblity
* Add support for PHP 7
* Improve unit tests
</notes>
</release>
<release>
<date>2015-10-30</date>
<version>
<release>2.2.0</release>
<api>2.2.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license>LGPLv3 License</license>
<notes>
* Fix bug #20969: Fatal error with PEAR 1.10.0 / constructor visiblity
* Add support for PHP 7
* Improve unit tests
</notes>
</release>
</changelog>
</package>
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
{
"authors": [
{
"email": "alec@alec.pl",
"name": "Aleksander Machniak",
"role": "Lead"
},
{
"email": "jan@horde.org",
"name": "Jan Schneider",
"role": "Lead"
},
{
"email": "richard@php.net",
"name": "Richard Heyes",
"role": "Lead"
},
{
"email": "damlists@cnba.uba.ar",
"name": "Damian Fernandez Sosa",
"role": "Lead"
},
{
"email": "amistry@am-productions.biz",
"name": "Anish Mistry",
"role": "Lead"
}
],
"autoload": {
"classmap": [
"./"
]
},
"description": "More info available on: https://pear.php.net/package/Net_Sieve",
"license": "BSD-2-Clause",
"name": "pear/net_sieve",
"support": {
"issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Sieve",
"source": "https://github.com/pear/Net_Sieve"
},
"type": "library",
"require": {
"pear/pear-core-minimal": "~1.10",
"pear/net_socket": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
},
"suggest": {
"pear/auth_sasl": "Install optionally via your project's composer.json"
}
}
+23
View File
@@ -0,0 +1,23 @@
Copyright 2002-2017 Jon Parise and Chuck Hagenbuch.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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..
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 HOLDER 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.
File diff suppressed because it is too large Load Diff
+298
View File
@@ -0,0 +1,298 @@
======================
The Net_SMTP Package
======================
--------------------
User Documentation
--------------------
:Author: Jon Parise
:Contact: jon@php.net
.. contents:: Table of Contents
.. section-numbering::
Dependencies
============
The ``PEAR_Error`` Class
------------------------
The Net_SMTP package uses the `PEAR_Error`_ class for all of its `error
handling`_.
The ``Net_Socket`` Package
--------------------------
The Net_Socket_ package is used as the basis for all network communications.
Connection options can be specified via the `$socket_options` construction
parameter::
$socket_options = array('ssl' => array('verify_peer_name' => false));
$smtp = new Net_SMTP($host, null, null, false, 0, $socket_options);
**Note:** PHP 5.6 introduced `OpenSSL changes`_. Peer certificate verification
is now enabled by default. Although not recommended, `$socket_options` can be
used to disable peer verification (as shown above).
.. _OpenSSL changes: https://php.net/manual/en/migration56.openssl.php
The ``Auth_SASL`` Package
-------------------------
The `Auth_SASL`_ package is an optional dependency. If it is available, the
Net_SMTP package will be able to support the DIGEST-MD5_ and CRAM-MD5_ SMTP
authentication methods. Otherwise, only the LOGIN_ and PLAIN_ methods will
be available.
Error Handling
==============
All of the Net_SMTP class's public methods return a PEAR_Error_ object if an
error occurs. The standard way to check for a PEAR_Error object is by using
`PEAR::isError()`_::
if (PEAR::isError($error = $smtp->connect())) {
die($error->getMessage());
}
.. _PEAR::isError(): https://pear.php.net/manual/en/core.pear.pear.iserror.php
SMTP Authentication
===================
The Net_SMTP package supports the SMTP authentication standard (as defined
by RFC-2554_). The Net_SMTP package supports the following authentication
methods, in order of preference:
.. _RFC-2554: https://www.ietf.org/rfc/rfc2554.txt
GSSAPI
------
The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_).
Does not use user/password.
Requires Service Principal ``gssapi_principal`` parameter and
has an optional Credentials Cache ``gssapi_cname`` parameter.
Requires DNS and Key Distribution Center (KDC) setup.
It is considered the most secure method of SMTP authentication.
**Note:** The GSSAPI authentication method is only supported
if the krb5_ php extension is available.
.. _RFC-4120: https://tools.ietf.org/html/rfc4120
.. _krb5: https://pecl.php.net/package/krb5
DIGEST-MD5
----------
The DIGEST-MD5 authentication method uses `RSA Data Security Inc.`_'s MD5
Message Digest algorithm. It is considered a more secure method of SMTP
authentication than PLAIN or LOGIN, while still vulnerable to MitM attacks
without TLS/SSL.
**Note:** The DIGEST-MD5 authentication method is only supported if the
AUTH_SASL_ package is available.
.. _RSA Data Security Inc.: https://www.rsasecurity.com/
CRAM-MD5
--------
The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_
method in terms of security. It is provided here for compatibility with
older SMTP servers that may not support the newer DIGEST-MD5 algorithm.
**Note:** The CRAM-MD5 authentication method is only supported if the
AUTH_SASL_ package is available.
LOGIN
-----
The LOGIN authentication method encrypts the user's password using the
Base64_ encoding scheme. Because decrypting a Base64-encoded string is
trivial, LOGIN is not considered a secure authentication method and should
be avoided.
.. _Base64: https://www.php.net/manual/en/function.base64-encode.php
PLAIN
-----
The PLAIN authentication method sends the user's password in plain text.
This method of authentication is not secure and should be avoided.
XOAUTH2
-------
The XOAUTH2 authentication method sends a username and an OAuth2 access token
as per `Gmail's SASL XOAUTH2 documentation`__.
.. __: https://developers.google.com/gmail/imap/xoauth2-protocol#smtp_protocol_exchange
Secure Connections
==================
If `secure socket transports`_ have been enabled in PHP, it is possible to
establish a secure connection to the remote SMTP server::
$smtp = new Net_SMTP('ssl://mail.example.com', 465);
This example connects to ``mail.example.com`` on port 465 (a common SMTPS
port) using the ``ssl://`` transport.
TLS/SSL is enabled for authenticated connections by default (via the ``auth()``
method's ``$tls`` parameter), but the |STARTTLS|_ command can also be sent
manually using the ``starttls()`` method.
.. _secure socket transports: https://www.php.net/transports
.. |STARTTLS| replace:: ``STARTTLS``
.. _STARTTLS: https://tools.ietf.org/html/rfc3207
Sending Data
============
Message data is sent using the ``data()`` method. The data can be supplied
as a single string or as an open file resource.
If a string is provided, it is passed through the `data quoting`_ system and
sent to the socket connection as a single block. These operations are all
memory-based, so sending large messages may result in high memory usage.
If an open file resource is provided, the ``data()`` method will read the
message data from the file line-by-line. Each chunk will be quoted and sent
to the socket connection individually, reducing the overall memory overhead of
this data sending operation.
Header data can be specified separately from message body data by passing it
as the optional second parameter to ``data()``. This is especially useful
when an open file resource is being used to supply message data because it
allows header fields (like *Subject:*) to be built dynamically at runtime.
::
$smtp->data($fp, "Subject: My Subject");
Data Quoting
============
By default, all outbound string data is quoted in accordance with SMTP
standards. This means that all native Unix (``\n``) and Mac (``\r``) line
endings are converted to Internet-standard CRLF (``\r\n``) line endings.
Also, because the SMTP protocol uses a single leading period (``.``) to signal
an end to the message data, single leading periods in the original data
string are "doubled" (e.g. "``..``").
These string transformation can be expensive when large blocks of data are
involved. For example, the Net_SMTP package is not aware of MIME parts (it
just sees the MIME message as one big string of characters), so it is not
able to skip non-text attachments when searching for characters that may
need to be quoted.
Because of this, it is possible to extend the Net_SMTP class in order to
implement your own custom quoting routine. Just create a new class based on
the Net_SMTP class and reimplement the ``quotedata()`` method::
require 'Net_SMTP.php';
class Net_SMTP_custom extends Net_SMTP
{
function quotedata($data)
{
/* Perform custom data quoting */
}
}
Note that the ``$data`` parameter will be passed to the ``quotedata()``
function `by reference`_. This means that you can operate directly on
``$data``. It also the overhead of copying a large ``$data`` string to and
from the ``quotedata()`` method.
.. _by reference: https://www.php.net/manual/en/language.references.pass.php
Server Responses
================
The Net_SMTP package retains the server's last response for further
inspection. The ``getResponse()`` method returns a 2-tuple (two element
array) containing the server's response code as an integer and the response's
arguments as a string.
Upon a successful connection, the server's greeting string is available via
the ``getGreeting()`` method.
Debugging
=========
The Net_SMTP package contains built-in debugging output routines (disabled by
default). Debugging output must be explicitly enabled via the ``setDebug()``
method::
$smtp->setDebug(true);
The debugging messages will be sent to the standard output stream by default.
If you need more control over the output, you can optionally install your own
debug handler.
::
function debugHandler($smtp, $message)
{
echo "[$smtp->host] $message\n";
}
$smtp->setDebug(true, "debugHandler");
Examples
========
Basic Use
---------
The following script demonstrates how a simple email message can be sent
using the Net_SMTP package::
require 'Net/SMTP.php';
$host = 'mail.example.com';
$from = 'user@example.com';
$rcpt = array('recipient1@example.com', 'recipient2@example.com');
$subj = "Subject: Test Message\n";
$body = "Body Line 1\nBody Line 2";
/* Create a new Net_SMTP object. */
if (! ($smtp = new Net_SMTP($host))) {
die("Unable to instantiate Net_SMTP object\n");
}
/* Connect to the SMTP server. */
if (PEAR::isError($e = $smtp->connect())) {
die($e->getMessage() . "\n");
}
/* Send the 'MAIL FROM:' SMTP command. */
if (PEAR::isError($smtp->mailFrom($from))) {
die("Unable to set sender to <$from>\n");
}
/* Address the message to each of the recipients. */
foreach ($rcpt as $to) {
if (PEAR::isError($res = $smtp->rcptTo($to))) {
die("Unable to add recipient <$to>: " . $res->getMessage() . "\n");
}
}
/* Set the body of the message. */
if (PEAR::isError($smtp->data($subj . "\r\n" . $body))) {
die("Unable to send data\n");
}
/* Disconnect from the SMTP server. */
$smtp->disconnect();
.. _PEAR_Error: https://pear.php.net/manual/en/core.pear.pear-error.php
.. _Net_Socket: https://pear.php.net/package/Net_Socket
.. _Auth_SASL: https://pear.php.net/package/Auth_SASL
.. vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab textwidth=78 ft=rst:
+53
View File
@@ -0,0 +1,53 @@
{
"authors": [
{
"email": "jon@php.net",
"name": "Jon Parise",
"homepage": "https://www.indelible.org",
"role": "Lead"
},
{
"email": "chuck@horde.org",
"name": "Chuck Hagenbuch",
"role": "Lead"
},
{
"email": "schengawegga@gmail.com",
"name": "Armin Graefe",
"role": "Lead"
}
],
"autoload": {
"psr-0": {
"Net": "./"
}
},
"description": "An implementation of the SMTP protocol",
"keywords": [
"smtp",
"mail",
"email"
],
"include-path": [
"./"
],
"license": "BSD-2-Clause",
"name": "pear/net_smtp",
"homepage": "https://pear.github.io/Net_SMTP/",
"support": {
"issues": "https://github.com/pear/Net_SMTP/issues",
"source": "https://github.com/pear/Net_SMTP"
},
"type": "library",
"require": {
"php": ">=5.4.0",
"pear/pear-core-minimal": "@stable",
"pear/net_socket": "@stable"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"pear/auth_sasl": "Install optionally via your project's composer.json"
}
}
+686
View File
@@ -0,0 +1,686 @@
<?php
/**
* Net_Socket
*
* PHP Version 4
*
* Copyright (c) 1997-2013 The PHP Group
*
* This source file is subject to version 2.0 of the PHP license,
* that is bundled with this package in the file LICENSE, and is
* available at through the world-wide-web at
* http://www.php.net/license/2_02.txt.
* If you did not receive a copy of the PHP license and are unable to
* obtain it through the world-wide-web, please send a note to
* license@php.net so we can mail you a copy immediately.
*
* Authors: Stig Bakken <ssb@php.net>
* Chuck Hagenbuch <chuck@horde.org>
*
* @category Net
* @package Net_Socket
* @author Stig Bakken <ssb@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 1997-2003 The PHP Group
* @license http://www.php.net/license/2_02.txt PHP 2.02
* @link http://pear.php.net/packages/Net_Socket
*/
require_once 'PEAR.php';
define('NET_SOCKET_READ', 1);
define('NET_SOCKET_WRITE', 2);
define('NET_SOCKET_ERROR', 4);
/**
* Generalized Socket class.
*
* @category Net
* @package Net_Socket
* @author Stig Bakken <ssb@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 1997-2003 The PHP Group
* @license http://www.php.net/license/2_02.txt PHP 2.02
* @link http://pear.php.net/packages/Net_Socket
*/
class Net_Socket extends PEAR
{
/**
* Socket file pointer.
* @var resource $fp
*/
var $fp = null;
/**
* Whether the socket is blocking. Defaults to true.
* @var boolean $blocking
*/
var $blocking = true;
/**
* Whether the socket is persistent. Defaults to false.
* @var boolean $persistent
*/
var $persistent = false;
/**
* The IP address to connect to.
* @var string $addr
*/
var $addr = '';
/**
* The port number to connect to.
* @var integer $port
*/
var $port = 0;
/**
* Number of seconds to wait on socket operations before assuming
* there's no more data. Defaults to no timeout.
* @var integer|float $timeout
*/
var $timeout = null;
/**
* Number of bytes to read at a time in readLine() and
* readAll(). Defaults to 2048.
* @var integer $lineLength
*/
var $lineLength = 2048;
/**
* The string to use as a newline terminator. Usually "\r\n" or "\n".
* @var string $newline
*/
var $newline = "\r\n";
/**
* Connect to the specified port. If called when the socket is
* already connected, it disconnects and connects again.
*
* @param string $addr IP address or host name (may be with protocol prefix).
* @param integer $port TCP port number.
* @param boolean $persistent (optional) Whether the connection is
* persistent (kept open between requests
* by the web server).
* @param integer $timeout (optional) Connection socket timeout.
* @param array $options See options for stream_context_create.
*
* @access public
*
* @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
*/
function connect($addr, $port = 0, $persistent = null,
$timeout = null, $options = null)
{
if (is_resource($this->fp)) {
@fclose($this->fp);
$this->fp = null;
}
if (!$addr) {
return $this->raiseError('$addr cannot be empty');
} else if (strspn($addr, ':.0123456789') == strlen($addr)) {
$this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr;
} else {
$this->addr = $addr;
}
$this->port = $port % 65536;
if ($persistent !== null) {
$this->persistent = $persistent;
}
$openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
$errno = 0;
$errstr = '';
$old_track_errors = @ini_set('track_errors', 1);
if ($timeout <= 0) {
$timeout = @ini_get('default_socket_timeout');
}
if ($options && function_exists('stream_context_create')) {
$context = stream_context_create($options);
// Since PHP 5 fsockopen doesn't allow context specification
if (function_exists('stream_socket_client')) {
$flags = STREAM_CLIENT_CONNECT;
if ($this->persistent) {
$flags = STREAM_CLIENT_PERSISTENT;
}
$addr = $this->addr . ':' . $this->port;
$fp = stream_socket_client($addr, $errno, $errstr,
$timeout, $flags, $context);
} else {
$fp = @$openfunc($this->addr, $this->port, $errno,
$errstr, $timeout, $context);
}
} else {
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout);
}
if (!$fp) {
if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) {
$errstr = $php_errormsg;
}
@ini_set('track_errors', $old_track_errors);
return $this->raiseError($errstr, $errno);
}
@ini_set('track_errors', $old_track_errors);
$this->fp = $fp;
$this->setTimeout();
return $this->setBlocking($this->blocking);
}
/**
* Disconnects from the peer, closes the socket.
*
* @access public
* @return mixed true on success or a PEAR_Error instance otherwise
*/
function disconnect()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
@fclose($this->fp);
$this->fp = null;
return true;
}
/**
* Set the newline character/sequence to use.
*
* @param string $newline Newline character(s)
* @return boolean True
*/
function setNewline($newline)
{
$this->newline = $newline;
return true;
}
/**
* Find out if the socket is in blocking mode.
*
* @access public
* @return boolean The current blocking mode.
*/
function isBlocking()
{
return $this->blocking;
}
/**
* Sets whether the socket connection should be blocking or
* not. A read call to a non-blocking socket will return immediately
* if there is no data available, whereas it will block until there
* is data for blocking sockets.
*
* @param boolean $mode True for blocking sockets, false for nonblocking.
*
* @access public
* @return mixed true on success or a PEAR_Error instance otherwise
*/
function setBlocking($mode)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$this->blocking = $mode;
stream_set_blocking($this->fp, (int)$this->blocking);
return true;
}
/**
* Sets the timeout value on socket descriptor,
* expressed in the sum of seconds and microseconds
*
* @param integer $seconds Seconds.
* @param integer $microseconds Microseconds, optional.
*
* @access public
* @return mixed True on success or false on failure or
* a PEAR_Error instance when not connected
*/
function setTimeout($seconds = null, $microseconds = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if ($seconds === null && $microseconds === null) {
$seconds = (int) $this->timeout;
$microseconds = (int) (($this->timeout - $seconds) * 1000000);
} else {
$this->timeout = $seconds + $microseconds/1000000;
}
if ($this->timeout > 0) {
return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds);
}
else {
return false;
}
}
/**
* Sets the file buffering size on the stream.
* See php's stream_set_write_buffer for more information.
*
* @param integer $size Write buffer size.
*
* @access public
* @return mixed on success or an PEAR_Error object otherwise
*/
function setWriteBuffer($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$returned = stream_set_write_buffer($this->fp, $size);
if ($returned == 0) {
return true;
}
return $this->raiseError('Cannot set write buffer.');
}
/**
* Returns information about an existing socket resource.
* Currently returns four entries in the result array:
*
* <p>
* timed_out (bool) - The socket timed out waiting for data<br>
* blocked (bool) - The socket was blocked<br>
* eof (bool) - Indicates EOF event<br>
* unread_bytes (int) - Number of bytes left in the socket buffer<br>
* </p>
*
* @access public
* @return mixed Array containing information about existing socket
* resource or a PEAR_Error instance otherwise
*/
function getStatus()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return stream_get_meta_data($this->fp);
}
/**
* Get a specified line of data
*
* @param int $size Reading ends when size - 1 bytes have been read,
* or a newline or an EOF (whichever comes first).
* If no size is specified, it will keep reading from
* the stream until it reaches the end of the line.
*
* @access public
* @return mixed $size bytes of data from the socket, or a PEAR_Error if
* not connected. If an error occurs, FALSE is returned.
*/
function gets($size = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if (is_null($size)) {
return @fgets($this->fp);
} else {
return @fgets($this->fp, $size);
}
}
/**
* Read a specified amount of data. This is guaranteed to return,
* and has the added benefit of getting everything in one fread()
* chunk; if you know the size of the data you're getting
* beforehand, this is definitely the way to go.
*
* @param integer $size The number of bytes to read from the socket.
*
* @access public
* @return $size bytes of data from the socket, or a PEAR_Error if
* not connected.
*/
function read($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @fread($this->fp, $size);
}
/**
* Write a specified amount of data.
*
* @param string $data Data to write.
* @param integer $blocksize Amount of data to write at once.
* NULL means all at once.
*
* @access public
* @return mixed If the socket is not connected, returns an instance of
* PEAR_Error.
* If the write succeeds, returns the number of bytes written.
* If the write fails, returns false.
* If the socket times out, returns an instance of PEAR_Error.
*/
function write($data, $blocksize = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if (is_null($blocksize) && !OS_WINDOWS) {
$written = @fwrite($this->fp, $data);
// Check for timeout or lost connection
if ($written===false) {
$meta_data = $this->getStatus();
if (!is_array($meta_data)) {
return $meta_data; // PEAR_Error
}
if (!empty($meta_data['timed_out'])) {
return $this->raiseError('timed out');
}
}
return $written;
} else {
if (is_null($blocksize)) {
$blocksize = 1024;
}
$pos = 0;
$size = strlen($data);
while ($pos < $size) {
$written = @fwrite($this->fp, substr($data, $pos, $blocksize));
// Check for timeout or lost connection
if ($written===false) {
$meta_data = $this->getStatus();
if (!is_array($meta_data)) {
return $meta_data; // PEAR_Error
}
if (!empty($meta_data['timed_out'])) {
return $this->raiseError('timed out');
}
return $written;
}
$pos += $written;
}
return $pos;
}
}
/**
* Write a line of data to the socket, followed by a trailing newline.
*
* @param string $data Data to write
*
* @access public
* @return mixed fwrite() result, or PEAR_Error when not connected
*/
function writeLine($data)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return fwrite($this->fp, $data . $this->newline);
}
/**
* Tests for end-of-file on a socket descriptor.
*
* Also returns true if the socket is disconnected.
*
* @access public
* @return bool
*/
function eof()
{
return (!is_resource($this->fp) || feof($this->fp));
}
/**
* Reads a byte of data
*
* @access public
* @return 1 byte of data from the socket, or a PEAR_Error if
* not connected.
*/
function readByte()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return ord(@fread($this->fp, 1));
}
/**
* Reads a word of data
*
* @access public
* @return 1 word of data from the socket, or a PEAR_Error if
* not connected.
*/
function readWord()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 2);
return (ord($buf[0]) + (ord($buf[1]) << 8));
}
/**
* Reads an int of data
*
* @access public
* @return integer 1 int of data from the socket, or a PEAR_Error if
* not connected.
*/
function readInt()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return (ord($buf[0]) + (ord($buf[1]) << 8) +
(ord($buf[2]) << 16) + (ord($buf[3]) << 24));
}
/**
* Reads a zero-terminated string of data
*
* @access public
* @return string, or a PEAR_Error if
* not connected.
*/
function readString()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$string = '';
while (($char = @fread($this->fp, 1)) != "\x00") {
$string .= $char;
}
return $string;
}
/**
* Reads an IP Address and returns it in a dot formatted string
*
* @access public
* @return Dot formatted string, or a PEAR_Error if
* not connected.
*/
function readIPAddress()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
ord($buf[2]), ord($buf[3]));
}
/**
* Read until either the end of the socket or a newline, whichever
* comes first. Strips the trailing newline from the returned data.
*
* @access public
* @return All available data up to a newline, without that
* newline, or until the end of the socket, or a PEAR_Error if
* not connected.
*/
function readLine()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$line = '';
$timeout = time() + $this->timeout;
while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
$line .= @fgets($this->fp, $this->lineLength);
if (substr($line, -1) == "\n") {
return rtrim($line, $this->newline);
}
}
return $line;
}
/**
* Read until the socket closes, or until there is no more data in
* the inner PHP buffer. If the inner buffer is empty, in blocking
* mode we wait for at least 1 byte of data. Therefore, in
* blocking mode, if there is no data at all to be read, this
* function will never exit (unless the socket is closed on the
* remote end).
*
* @access public
*
* @return string All data until the socket closes, or a PEAR_Error if
* not connected.
*/
function readAll()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$data = '';
while (!feof($this->fp)) {
$data .= @fread($this->fp, $this->lineLength);
}
return $data;
}
/**
* Runs the equivalent of the select() system call on the socket
* with a timeout specified by tv_sec and tv_usec.
*
* @param integer $state Which of read/write/error to check for.
* @param integer $tv_sec Number of seconds for timeout.
* @param integer $tv_usec Number of microseconds for timeout.
*
* @access public
* @return False if select fails, integer describing which of read/write/error
* are ready, or PEAR_Error if not connected.
*/
function select($state, $tv_sec, $tv_usec = 0)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$read = null;
$write = null;
$except = null;
if ($state & NET_SOCKET_READ) {
$read[] = $this->fp;
}
if ($state & NET_SOCKET_WRITE) {
$write[] = $this->fp;
}
if ($state & NET_SOCKET_ERROR) {
$except[] = $this->fp;
}
if (false === ($sr = stream_select($read, $write, $except,
$tv_sec, $tv_usec))) {
return false;
}
$result = 0;
if (count($read)) {
$result |= NET_SOCKET_READ;
}
if (count($write)) {
$result |= NET_SOCKET_WRITE;
}
if (count($except)) {
$result |= NET_SOCKET_ERROR;
}
return $result;
}
/**
* Turns encryption on/off on a connected socket.
*
* @param bool $enabled Set this parameter to true to enable encryption
* and false to disable encryption.
* @param integer $type Type of encryption. See stream_socket_enable_crypto()
* for values.
*
* @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php
* @access public
* @return false on error, true on success and 0 if there isn't enough data
* and the user should try again (non-blocking sockets only).
* A PEAR_Error object is returned if the socket is not
* connected
*/
function enableCrypto($enabled, $type)
{
if (version_compare(phpversion(), "5.1.0", ">=")) {
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @stream_socket_enable_crypto($this->fp, $enabled, $type);
} else {
$msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0';
return $this->raiseError($msg);
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"authors": [
{
"email": "chuck@horde.org",
"name": "Chuck Hagenbuch",
"role": "Lead"
},
{
"email": "stig@php.net",
"name": "Stig Bakken",
"role": "Lead"
},
{
"email": "alec@php.net",
"name": "Aleksander Machniak",
"role": "Lead"
}
],
"autoload": {
"psr-0": {
"Net": "./"
}
},
"description": "More info available on: http://pear.php.net/package/Net_Socket",
"include-path": [
"./"
],
"license": "PHP License",
"name": "pear/net_socket",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Socket",
"source": "https://github.com/pear/Net_Socket"
},
"type": "library",
"require": {
"pear/pear_exception": "*"
},
"require-dev": {
"phpunit/phpunit": "*"
}
}
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>Net_Socket</name>
<channel>pear.php.net</channel>
<summary>Network Socket Interface</summary>
<description>Net_Socket is a class interface to TCP sockets. It provides blocking
and non-blocking operation, with different reading and writing modes
(byte-wise, block-wise, line-wise and special formats like network
byte-order ip addresses).</description>
<lead>
<name>Chuck Hagenbuch</name>
<user>chagenbu</user>
<email>chuck@horde.org</email>
<active>yes</active>
</lead>
<lead>
<name>Stig Bakken</name>
<user>ssb</user>
<email>stig@php.net</email>
<active>no</active>
</lead>
<lead>
<name>Aleksander Machniak</name>
<user>alec</user>
<email>alec@php.net</email>
<active>yes</active>
</lead>
<date>2013-05-24</date>
<time>20:00:00</time>
<version>
<release>1.0.14</release>
<api>1.0.10</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.php.net/license/2_02.txt">PHP License</license>
<notes>
- Fix connecting when host is specified with protocol prefix e.g. ssl://
</notes>
<contents>
<dir baseinstalldir="/" name="/">
<file baseinstalldir="/" md5sum="057d5c52b2dd9cfb2a458d532d95cfbb" name="Net/Socket.php" role="php" />
</dir>
</contents>
<dependencies>
<required>
<php>
<min>4.3.0</min>
</php>
<pearinstaller>
<min>1.4.0b1</min>
</pearinstaller>
</required>
</dependencies>
<phprelease />
</package>
+26
View File
@@ -0,0 +1,26 @@
******************************
Minimal set of PEAR core files
******************************
This repository provides a set of files from ``pear-core``
that are often used in PEAR packages.
It follows the `pear-core`__ repository and gets updated whenever a new
PEAR version is released.
It's meant to be used as dependency for composer packages.
__ https://github.com/pear/pear-core
==============
Included files
==============
- ``OS/Guess.php``
- ``PEAR.php``
- ``PEAR/Error.php``
- ``PEAR/ErrorStack.php``
- ``System.php``
``PEAR/Error.php`` is a dummy file that only includes ``PEAR.php``,
to make autoloaders work without problems.
+32
View File
@@ -0,0 +1,32 @@
{
"name": "pear/pear-core-minimal",
"description": "Minimal set of PEAR core files to be used as composer dependency",
"license": "BSD-3-Clause",
"authors": [
{
"email": "cweiske@php.net",
"name": "Christian Weiske",
"role": "Lead"
}
],
"autoload": {
"psr-0": {
"": "src/"
}
},
"include-path": [
"src/"
],
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR",
"source": "https://github.com/pear/pear-core-minimal"
},
"type": "library",
"require": {
"pear/console_getopt": "~1.4",
"pear/pear_exception": "~1.0"
},
"replace": {
"rsky/pear-core-min": "self.version"
}
}
@@ -0,0 +1,395 @@
<?php
/**
* The OS_Guess class
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since PEAR 0.1
*/
// {{{ uname examples
// php_uname() without args returns the same as 'uname -a', or a PHP-custom
// string for Windows.
// PHP versions prior to 4.3 return the uname of the host where PHP was built,
// as of 4.3 it returns the uname of the host running the PHP code.
//
// PC RedHat Linux 7.1:
// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
//
// PC Debian Potato:
// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
//
// PC FreeBSD 3.3:
// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.3:
// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5 w/uname from GNU shellutils:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown
//
// HP 9000/712 HP-UX 10:
// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
//
// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
// HP-UX host B.10.10 A 9000/712 unknown
//
// IBM RS6000/550 AIX 4.3:
// AIX host 3 4 000003531C00
//
// AIX 4.3 w/uname from GNU shellutils:
// AIX host 3 4 000003531C00 unknown
//
// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
// IRIX64 host 6.5 01091820 IP19 mips
//
// SGI Onyx IRIX 6.5:
// IRIX64 host 6.5 01091820 IP19
//
// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
//
// SparcStation 20 Solaris 8:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
//
// Mac OS X (Darwin)
// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh
//
// Mac OS X early versions
//
// }}}
/* TODO:
* - define endianness, to allow matchSignature("bigend") etc.
*/
/**
* Retrieves information about the current operating system
*
* This class uses php_uname() to grok information about the current OS
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2020 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
*/
class OS_Guess
{
var $sysname;
var $nodename;
var $cpu;
var $release;
var $extra;
function __construct($uname = null)
{
list($this->sysname,
$this->release,
$this->cpu,
$this->extra,
$this->nodename) = $this->parseSignature($uname);
}
function parseSignature($uname = null)
{
static $sysmap = array(
'HP-UX' => 'hpux',
'IRIX64' => 'irix',
);
static $cpumap = array(
'i586' => 'i386',
'i686' => 'i386',
'ppc' => 'powerpc',
);
if ($uname === null) {
$uname = php_uname();
}
$parts = preg_split('/\s+/', trim($uname));
$n = count($parts);
$release = $machine = $cpu = '';
$sysname = $parts[0];
$nodename = $parts[1];
$cpu = $parts[$n-1];
$extra = '';
if ($cpu == 'unknown') {
$cpu = $parts[$n - 2];
}
switch ($sysname) {
case 'AIX' :
$release = "$parts[3].$parts[2]";
break;
case 'Windows' :
$release = $parts[1];
if ($release == '95/98') {
$release = '9x';
}
$cpu = 'i386';
break;
case 'Linux' :
$extra = $this->_detectGlibcVersion();
// use only the first two digits from the kernel version
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
case 'Mac' :
$sysname = 'darwin';
$nodename = $parts[2];
$release = $parts[3];
$cpu = $this->_determineIfPowerpc($cpu, $parts);
break;
case 'Darwin' :
$cpu = $this->_determineIfPowerpc($cpu, $parts);
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
default:
$release = preg_replace('/-.*/', '', $parts[2]);
break;
}
if (isset($sysmap[$sysname])) {
$sysname = $sysmap[$sysname];
} else {
$sysname = strtolower($sysname);
}
if (isset($cpumap[$cpu])) {
$cpu = $cpumap[$cpu];
}
return array($sysname, $release, $cpu, $extra, $nodename);
}
function _determineIfPowerpc($cpu, $parts)
{
$n = count($parts);
if ($cpu == 'Macintosh' && $parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
return $cpu;
}
function _detectGlibcVersion()
{
static $glibc = false;
if ($glibc !== false) {
return $glibc; // no need to run this multiple times
}
$major = $minor = 0;
include_once "System.php";
// Let's try reading possible libc.so.6 symlinks
$libcs = array(
'/lib64/libc.so.6',
'/lib/libc.so.6',
'/lib/i386-linux-gnu/libc.so.6'
);
$versions = array();
foreach ($libcs as $file) {
$versions = $this->_readGlibCVersionFromSymlink($file);
if ($versions != []) {
list($major, $minor) = $versions;
break;
}
}
// Use glibc's <features.h> header file to
// get major and minor version number:
if (!($major && $minor)) {
$versions = $this->_readGlibCVersionFromFeaturesHeaderFile();
}
if (is_array($versions) && $versions != []) {
list($major, $minor) = $versions;
}
if (!($major && $minor)) {
return $glibc = '';
}
return $glibc = "glibc{$major}.{$minor}";
}
function _readGlibCVersionFromSymlink($file)
{
$versions = array();
if (@is_link($file)
&& (preg_match('/^libc-(.*)\.so$/', basename(readlink($file)), $matches))
) {
$versions = explode('.', $matches[1]);
}
return $versions;
}
function _readGlibCVersionFromFeaturesHeaderFile()
{
$features_header_file = '/usr/include/features.h';
if (!(@file_exists($features_header_file)
&& @is_readable($features_header_file))
) {
return array();
}
if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
return $this->_parseFeaturesHeaderFile($features_header_file);
} // no cpp
return $this->_fromGlibCTest();
}
function _parseFeaturesHeaderFile($features_header_file)
{
$features_file = fopen($features_header_file, 'rb');
while (!feof($features_file)) {
$line = fgets($features_file, 8192);
if (!$this->_IsADefinition($line)) {
continue;
}
if (strpos($line, '__GLIBC__')) {
// major version number #define __GLIBC__ version
$line = preg_split('/\s+/', $line);
$glibc_major = trim($line[2]);
if (isset($glibc_minor)) {
break;
}
continue;
}
if (strpos($line, '__GLIBC_MINOR__')) {
// got the minor version number
// #define __GLIBC_MINOR__ version
$line = preg_split('/\s+/', $line);
$glibc_minor = trim($line[2]);
if (isset($glibc_major)) {
break;
}
}
}
fclose($features_file);
if (!isset($glibc_major) || !isset($glibc_minor)) {
return array();
}
return array(trim($glibc_major), trim($glibc_minor));
}
function _IsADefinition($line)
{
if ($line === false) {
return false;
}
return strpos(trim($line), '#define') !== false;
}
function _fromGlibCTest()
{
$major = null;
$minor = null;
$tmpfile = System::mktemp("glibctest");
$fp = fopen($tmpfile, "w");
fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
fclose($fp);
$cpp = popen("/usr/bin/cpp $tmpfile", "r");
while ($line = fgets($cpp, 1024)) {
if ($line[0] == '#' || trim($line) == '') {
continue;
}
if (list($major, $minor) = explode(' ', trim($line))) {
break;
}
}
pclose($cpp);
unlink($tmpfile);
if ($major !== null && $minor !== null) {
return [$major, $minor];
}
}
function getSignature()
{
if (empty($this->extra)) {
return "{$this->sysname}-{$this->release}-{$this->cpu}";
}
return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
}
function getSysname()
{
return $this->sysname;
}
function getNodename()
{
return $this->nodename;
}
function getCpu()
{
return $this->cpu;
}
function getRelease()
{
return $this->release;
}
function getExtra()
{
return $this->extra;
}
function matchSignature($match)
{
$fragments = is_array($match) ? $match : explode('-', $match);
$n = count($fragments);
$matches = 0;
if ($n > 0) {
$matches += $this->_matchFragment($fragments[0], $this->sysname);
}
if ($n > 1) {
$matches += $this->_matchFragment($fragments[1], $this->release);
}
if ($n > 2) {
$matches += $this->_matchFragment($fragments[2], $this->cpu);
}
if ($n > 3) {
$matches += $this->_matchFragment($fragments[3], $this->extra);
}
return ($matches == $n);
}
function _matchFragment($fragment, $value)
{
if (strcspn($fragment, '*?') < strlen($fragment)) {
$expression = str_replace(
array('*', '?', '/'),
array('.*', '.', '\\/'),
$fragment
);
$reg = '/^' . $expression . '\\z/';
return preg_match($reg, $value);
}
return ($fragment == '*' || !strcasecmp($fragment, $value));
}
}
/*
* Local Variables:
* indent-tabs-mode: nil
* c-basic-offset: 4
* End:
*/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
<?php
/**
* Dummy file to make autoloaders work
*
* PHP version 5
*
* @category PEAR
* @package PEAR
* @author Christian Weiske <cweiske@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
*/
require_once __DIR__ . '/../PEAR.php';
?>
@@ -0,0 +1,979 @@
<?php
/**
* Error Stack Implementation
*
* This is an incredibly simple implementation of a very complex error handling
* facility. It contains the ability
* to track multiple errors from multiple packages simultaneously. In addition,
* it can track errors of many levels, save data along with the error, context
* information such as the exact file, line number, class and function that
* generated the error, and if necessary, it can raise a traditional PEAR_Error.
* It has built-in support for PEAR::Log, to log errors as they occur
*
* Since version 0.2alpha, it is also possible to selectively ignore errors,
* through the use of an error callback, see {@link pushCallback()}
*
* Since version 0.3alpha, it is possible to specify the exception class
* returned from {@link push()}
*
* Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
* still be done quite handily in an error callback or by manipulating the returned array
* @category Debugging
* @package PEAR_ErrorStack
* @author Greg Beaver <cellog@php.net>
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
/**
* Singleton storage
*
* Format:
* <pre>
* array(
* 'package1' => PEAR_ErrorStack object,
* 'package2' => PEAR_ErrorStack object,
* ...
* )
* </pre>
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
*/
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
/**
* Global error callback (default)
*
* This is only used if set to non-false. * is the default callback for
* all packages, whereas specific packages may set a default callback
* for all instances, regardless of whether they are a singleton or not.
*
* To exclude non-singletons, only set the local callback for the singleton
* @see PEAR_ErrorStack::setDefaultCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
'*' => false,
);
/**
* Global Log object (default)
*
* This is only used if set to non-false. Use to set a default log object for
* all stacks, regardless of instantiation order or location
* @see PEAR_ErrorStack::setDefaultLogger()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
/**
* Global Overriding Callback
*
* This callback will override any error callbacks that specific loggers have set.
* Use with EXTREME caution
* @see PEAR_ErrorStack::staticPushCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
/**#@+
* One of four possible return values from the error Callback
* @see PEAR_ErrorStack::_errorCallback()
*/
/**
* If this is returned, then the error will be both pushed onto the stack
* and logged.
*/
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
* If this is returned, then the error will only be pushed onto the stack,
* and not logged.
*/
define('PEAR_ERRORSTACK_PUSH', 2);
/**
* If this is returned, then the error will only be logged, but not pushed
* onto the error stack.
*/
define('PEAR_ERRORSTACK_LOG', 3);
/**
* If this is returned, then the error is completely ignored.
*/
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
* If this is returned, then the error is logged and die() is called.
*/
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/
/**
* Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
* the singleton method.
*/
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
/**
* Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
* that has no __toString() method
*/
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
* Error Stack Implementation
*
* Usage:
* <code>
* // global error stack
* $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
* // local error stack
* $local_stack = new PEAR_ErrorStack('MyPackage');
* </code>
* @author Greg Beaver <cellog@php.net>
* @version @package_version@
* @package PEAR_ErrorStack
* @category Debugging
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
class PEAR_ErrorStack {
/**
* Errors are stored in the order that they are pushed on the stack.
* @since 0.4alpha Errors are no longer organized by error level.
* This renders pop() nearly unusable, and levels could be more easily
* handled in a callback anyway
* @var array
* @access private
*/
var $_errors = array();
/**
* Storage of errors by level.
*
* Allows easy retrieval and deletion of only errors from a particular level
* @since PEAR 1.4.0dev
* @var array
* @access private
*/
var $_errorsByLevel = array();
/**
* Package name this error stack represents
* @var string
* @access protected
*/
var $_package;
/**
* Determines whether a PEAR_Error is thrown upon every error addition
* @var boolean
* @access private
*/
var $_compat = false;
/**
* If set to a valid callback, this will be used to generate the error
* message from the error code, otherwise the message passed in will be
* used
* @var false|string|array
* @access private
*/
var $_msgCallback = false;
/**
* If set to a valid callback, this will be used to generate the error
* context for an error. For PHP-related errors, this will be a file
* and line number as retrieved from debug_backtrace(), but can be
* customized for other purposes. The error might actually be in a separate
* configuration file, or in a database query.
* @var false|string|array
* @access protected
*/
var $_contextCallback = false;
/**
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one an PEAR_ERRORSTACK_* constant
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @var false|string|array
* @access protected
*/
var $_errorCallback = array();
/**
* PEAR::Log object for logging errors
* @var false|Log
* @access protected
*/
var $_logger = false;
/**
* Error messages - designed to be overridden
* @var array
* @abstract
*/
var $_errorMsgs = array();
/**
* Set up a new error stack
*
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
*/
function __construct($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false)
{
$this->_package = $package;
$this->setMessageCallback($msgCallback);
$this->setContextCallback($contextCallback);
$this->_compat = $throwPEAR_Error;
}
/**
* Return a single error stack for this package.
*
* Note that all parameters are ignored if the stack for package $package
* has already been instantiated
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
* @param string $stackClass class to instantiate
*
* @return PEAR_ErrorStack
*/
public static function &singleton(
$package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack'
) {
if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
if (!class_exists($stackClass)) {
if (function_exists('debug_backtrace')) {
$trace = debug_backtrace();
}
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
'exception', array('stackclass' => $stackClass),
'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
false, $trace);
}
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
/**
* Internal error handler for PEAR_ErrorStack class
*
* Dies if the error is an exception (and would have died anyway)
* @access private
*/
function _handleError($err)
{
if ($err['level'] == 'exception') {
$message = $err['message'];
if (isset($_SERVER['REQUEST_URI'])) {
echo '<br />';
} else {
echo "\n";
}
var_dump($err['context']);
die($message);
}
}
/**
* Set up a PEAR::Log object for all error stacks that don't have one
* @param Log $log
*/
public static function setDefaultLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
} elseif (is_callable($log)) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
}
}
/**
* Set up a PEAR::Log object for this error stack
* @param Log $log
*/
function setLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$this->_logger = &$log;
} elseif (is_callable($log)) {
$this->_logger = &$log;
}
}
/**
* Set an error code => error message mapping callback
*
* This method sets the callback that can be used to generate error
* messages for any instance
* @param array|string Callback function/method
*/
function setMessageCallback($msgCallback)
{
if (!$msgCallback) {
$this->_msgCallback = array(&$this, 'getErrorMessage');
} else {
if (is_callable($msgCallback)) {
$this->_msgCallback = $msgCallback;
}
}
}
/**
* Get an error code => error message mapping callback
*
* This method returns the current callback that can be used to generate error
* messages
* @return array|string|false Callback function/method or false if none
*/
function getMessageCallback()
{
return $this->_msgCallback;
}
/**
* Sets a default callback to be used by all error stacks
*
* This method sets the callback that can be used to generate error
* messages for a singleton
* @param array|string Callback function/method
* @param string Package name, or false for all packages
*/
public static function setDefaultCallback($callback = false, $package = false)
{
if (!is_callable($callback)) {
$callback = false;
}
$package = $package ? $package : '*';
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
}
/**
* Set a callback that generates context information (location of error) for an error stack
*
* This method sets the callback that can be used to generate context
* information for an error. Passing in NULL will disable context generation
* and remove the expensive call to debug_backtrace()
* @param array|string|null Callback function/method
*/
function setContextCallback($contextCallback)
{
if ($contextCallback === null) {
return $this->_contextCallback = false;
}
if (!$contextCallback) {
$this->_contextCallback = array(&$this, 'getFileLine');
} else {
if (is_callable($contextCallback)) {
$this->_contextCallback = $contextCallback;
}
}
}
/**
* Set an error Callback
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one of the ERRORSTACK_* constants.
*
* This functionality can be used to emulate PEAR's pushErrorHandling, and
* the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
* the error stack or logging
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see popCallback()
* @param string|array $cb
*/
function pushCallback($cb)
{
array_push($this->_errorCallback, $cb);
}
/**
* Remove a callback from the error callback stack
* @see pushCallback()
* @return array|string|false
*/
function popCallback()
{
if (!count($this->_errorCallback)) {
return false;
}
return array_pop($this->_errorCallback);
}
/**
* Set a temporary overriding error callback for every package error stack
*
* Use this to temporarily disable all existing callbacks (can be used
* to emulate the @ operator, for instance)
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see staticPopCallback(), pushCallback()
* @param string|array $cb
*/
public static function staticPushCallback($cb)
{
array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
}
/**
* Remove a temporary overriding error callback
* @see staticPushCallback()
* @return array|string|false
*/
public static function staticPopCallback()
{
$ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
}
return $ret;
}
/**
* Add an error to the stack
*
* If the message generator exists, it is called with 2 parameters.
* - the current Error Stack object
* - an array that is in the same format as an error. Available indices
* are 'code', 'package', 'time', 'params', 'level', and 'context'
*
* Next, if the error should contain context information, this is
* handled by the context grabbing method.
* Finally, the error is pushed onto the proper error stack
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. If a PEAR_Error is returned, the userinfo
* property is set to the following array:
*
* <code>
* array(
* 'code' => $code,
* 'params' => $params,
* 'package' => $this->_package,
* 'level' => $level,
* 'time' => time(),
* 'context' => $context,
* 'message' => $msg,
* //['repackage' => $err] repackaged error array/Exception class
* );
* </code>
*
* Normally, the previous array is returned.
*/
function push($code, $level = 'error', $params = array(), $msg = false,
$repackage = false, $backtrace = false)
{
$context = false;
// grab error context
if ($this->_contextCallback) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
}
// save error
$time = explode(' ', microtime());
$time = $time[1] + $time[0];
$err = array(
'code' => $code,
'params' => $params,
'package' => $this->_package,
'level' => $level,
'time' => $time,
'context' => $context,
'message' => $msg,
);
if ($repackage) {
$err['repackage'] = $repackage;
}
// set up the error message, if necessary
if ($this->_msgCallback) {
$msg = call_user_func_array($this->_msgCallback,
array(&$this, $err));
$err['message'] = $msg;
}
$push = $log = true;
$die = false;
// try the overriding callback first
$callback = $this->staticPopCallback();
if ($callback) {
$this->staticPushCallback($callback);
}
if (!is_callable($callback)) {
// try the local callback next
$callback = $this->popCallback();
if (is_callable($callback)) {
$this->pushCallback($callback);
} else {
// try the default callback
$callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
}
}
if (is_callable($callback)) {
switch(call_user_func($callback, $err)){
case PEAR_ERRORSTACK_IGNORE:
return $err;
break;
case PEAR_ERRORSTACK_PUSH:
$log = false;
break;
case PEAR_ERRORSTACK_LOG:
$push = false;
break;
case PEAR_ERRORSTACK_DIE:
$die = true;
break;
// anything else returned has the same effect as pushandlog
}
}
if ($push) {
array_unshift($this->_errors, $err);
if (!isset($this->_errorsByLevel[$err['level']])) {
$this->_errorsByLevel[$err['level']] = array();
}
$this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
}
if ($log) {
if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
$this->_log($err);
}
}
if ($die) {
die();
}
if ($this->_compat && $push) {
return $this->raiseError($msg, $code, null, null, $err);
}
return $err;
}
/**
* Static version of {@link push()}
*
* @param string $package Package name this error belongs to
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. see docs for {@link push()}
*/
public static function staticPush(
$package, $code, $level = 'error', $params = array(),
$msg = false, $repackage = false, $backtrace = false
) {
$s = &PEAR_ErrorStack::singleton($package);
if ($s->_contextCallback) {
if (!$backtrace) {
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
}
}
}
return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
}
/**
* Log an error using PEAR::Log
* @param array $err Error array
* @param array $levels Error level => Log constant map
* @access protected
*/
function _log($err)
{
if ($this->_logger) {
$logger = &$this->_logger;
} else {
$logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
}
if (is_a($logger, 'Log')) {
$levels = array(
'exception' => PEAR_LOG_CRIT,
'alert' => PEAR_LOG_ALERT,
'critical' => PEAR_LOG_CRIT,
'error' => PEAR_LOG_ERR,
'warning' => PEAR_LOG_WARNING,
'notice' => PEAR_LOG_NOTICE,
'info' => PEAR_LOG_INFO,
'debug' => PEAR_LOG_DEBUG);
if (isset($levels[$err['level']])) {
$level = $levels[$err['level']];
} else {
$level = PEAR_LOG_INFO;
}
$logger->log($err['message'], $level, $err);
} else { // support non-standard logs
call_user_func($logger, $err);
}
}
/**
* Pop an error off of the error stack
*
* @return false|array
* @since 0.4alpha it is no longer possible to specify a specific error
* level to return - the last error pushed will be returned, instead
*/
function pop()
{
$err = @array_shift($this->_errors);
if (!is_null($err)) {
@array_pop($this->_errorsByLevel[$err['level']]);
if (!count($this->_errorsByLevel[$err['level']])) {
unset($this->_errorsByLevel[$err['level']]);
}
}
return $err;
}
/**
* Pop an error off of the error stack, static method
*
* @param string package name
* @return boolean
* @since PEAR1.5.0a1
*/
static function staticPop($package)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
}
}
/**
* Determine whether there are any errors on the stack
* @param string|array Level name. Use to determine if any errors
* of level (string), or levels (array) have been pushed
* @return boolean
*/
function hasErrors($level = false)
{
if ($level) {
return isset($this->_errorsByLevel[$level]);
}
return count($this->_errors);
}
/**
* Retrieve all errors since last purge
*
* @param boolean set in order to empty the error stack
* @param string level name, to return only errors of a particular severity
* @return array
*/
function getErrors($purge = false, $level = false)
{
if (!$purge) {
if ($level) {
if (!isset($this->_errorsByLevel[$level])) {
return array();
} else {
return $this->_errorsByLevel[$level];
}
} else {
return $this->_errors;
}
}
if ($level) {
$ret = $this->_errorsByLevel[$level];
foreach ($this->_errorsByLevel[$level] as $i => $unused) {
// entries are references to the $_errors array
$this->_errorsByLevel[$level][$i] = false;
}
// array_filter removes all entries === false
$this->_errors = array_filter($this->_errors);
unset($this->_errorsByLevel[$level]);
return $ret;
}
$ret = $this->_errors;
$this->_errors = array();
$this->_errorsByLevel = array();
return $ret;
}
/**
* Determine whether there are any errors on a single error stack, or on any error stack
*
* The optional parameter can be used to test the existence of any errors without the need of
* singleton instantiation
* @param string|false Package name to check for errors
* @param string Level name to check for a particular severity
* @return boolean
*/
public static function staticHasErrors($package = false, $level = false)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
if ($obj->hasErrors($level)) {
return true;
}
}
return false;
}
/**
* Get a list of all errors since last purge, organized by package
* @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
* @param boolean $purge Set to purge the error stack of existing errors
* @param string $level Set to a level name in order to retrieve only errors of a particular level
* @param boolean $merge Set to return a flat array, not organized by package
* @param array $sortfunc Function used to sort a merged array - default
* sorts by time, and should be good for most cases
*
* @return array
*/
public static function staticGetErrors(
$purge = false, $level = false, $merge = false,
$sortfunc = array('PEAR_ErrorStack', '_sortErrors')
) {
$ret = array();
if (!is_callable($sortfunc)) {
$sortfunc = array('PEAR_ErrorStack', '_sortErrors');
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
$test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
if ($test) {
if ($merge) {
$ret = array_merge($ret, $test);
} else {
$ret[$package] = $test;
}
}
}
if ($merge) {
usort($ret, $sortfunc);
}
return $ret;
}
/**
* Error sorting function, sorts by time
* @access private
*/
public static function _sortErrors($a, $b)
{
if ($a['time'] == $b['time']) {
return 0;
}
if ($a['time'] < $b['time']) {
return 1;
}
return -1;
}
/**
* Standard file/line number/function/class context callback
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error.
* @return array|false either array('file' => file, 'line' => line,
* 'function' => function name, 'class' => class name) or
* if this doesn't work, then false
* @param unused
* @param integer backtrace frame.
* @param array Results of debug_backtrace()
*/
public static function getFileLine($code, $params, $backtrace = null)
{
if ($backtrace === null) {
return false;
}
$frame = 0;
$functionframe = 1;
if (!isset($backtrace[1])) {
$functionframe = 0;
} else {
while (isset($backtrace[$functionframe]['function']) &&
$backtrace[$functionframe]['function'] == 'eval' &&
isset($backtrace[$functionframe + 1])) {
$functionframe++;
}
}
if (isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('file' => $filebacktrace['file'],
'line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (strpos($filebacktrace['file'], '(') &&
preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
$matches)) {
$ret['file'] = $matches[1];
$ret['line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
if ($funcbacktrace['function'] != 'eval') {
if ($funcbacktrace['function'] == '__lambda_func') {
$ret['function'] = 'create_function() code';
} else {
$ret['function'] = $funcbacktrace['function'];
}
}
}
if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
$ret['class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
/**
* Standard error message generation callback
*
* This method may also be called by a custom error message generator
* to fill in template values from the params array, simply
* set the third parameter to the error message template string to use
*
* The special variable %__msg% is reserved: use it only to specify
* where a message passed in by the user should be placed in the template,
* like so:
*
* Error message: %msg% - internal error
*
* If the message passed like so:
*
* <code>
* $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
* </code>
*
* The returned error message will be "Error message: server error 500 -
* internal error"
* @param PEAR_ErrorStack
* @param array
* @param string|false Pre-generated error message template
*
* @return string
*/
public static function getErrorMessage(&$stack, $err, $template = false)
{
if ($template) {
$mainmsg = $template;
} else {
$mainmsg = $stack->getErrorMessageTemplate($err['code']);
}
$mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
if (is_array($err['params']) && count($err['params'])) {
foreach ($err['params'] as $name => $val) {
if (is_array($val)) {
// @ is needed in case $val is a multi-dimensional array
$val = @implode(', ', $val);
}
if (is_object($val)) {
if (method_exists($val, '__toString')) {
$val = $val->__toString();
} else {
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
'warning', array('obj' => get_class($val)),
'object %obj% passed into getErrorMessage, but has no __toString() method');
$val = 'Object';
}
}
$mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
}
}
return $mainmsg;
}
/**
* Standard Error Message Template generator from code
* @return string
*/
function getErrorMessageTemplate($code)
{
if (!isset($this->_errorMsgs[$code])) {
return '%__msg%';
}
return $this->_errorMsgs[$code];
}
/**
* Set the Error Message Template array
*
* The array format must be:
* <pre>
* array(error code => 'message template',...)
* </pre>
*
* Error message parameters passed into {@link push()} will be used as input
* for the error message. If the template is 'message %foo% was %bar%', and the
* parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
* be 'message one was six'
* @return string
*/
function setErrorMessageTemplate($template)
{
$this->_errorMsgs = $template;
}
/**
* emulate PEAR::raiseError()
*
* @return PEAR_Error
*/
function raiseError()
{
require_once 'PEAR.php';
$args = func_get_args();
return call_user_func_array(array('PEAR', 'raiseError'), $args);
}
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>
+630
View File
@@ -0,0 +1,630 @@
<?php
/**
* File/Directory manipulation
*
* PHP versions 4 and 5
*
* @category pear
* @package System
* @author Tomas V.V.Cox <cox@idecnet.com>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**
* base class
*/
require_once 'PEAR.php';
require_once 'Console/Getopt.php';
$GLOBALS['_System_temp_files'] = array();
/**
* System offers cross platform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its respectively
* GNU commands. The functions will return (bool) false on error and will
* trigger the error with the PHP trigger_error() function (you can silence
* the error by prefixing a '@' sign after the function call, but this
* is not recommended practice. Instead use an error handler with
* {@link set_error_handler()}).
*
* Documentation on this class you can find in:
* http://pear.php.net/manual/
*
* Example usage:
* if (!@System::rm('-r file1 dir1')) {
* print "could not delete file1 or dir1";
* }
*
* In case you need to to pass file names with spaces,
* pass the params as an array:
*
* System::rm(array('-r', $file1, $dir1));
*
* @category pear
* @package System
* @author Tomas V.V. Cox <cox@idecnet.com>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
* @static
*/
class System
{
/**
* returns the commandline arguments of a function
*
* @param string $argv the commandline
* @param string $short_options the allowed option short-tags
* @param string $long_options the allowed option long-tags
* @return array the given options and there values
*/
public static function _parseArgs($argv, $short_options, $long_options = null)
{
if (!is_array($argv) && $argv !== null) {
/*
// Quote all items that are a short option
$av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
$offset = 0;
foreach ($av as $a) {
$b = trim($a[0]);
if ($b[0] == '"' || $b[0] == "'") {
continue;
}
$escape = escapeshellarg($b);
$pos = $a[1] + $offset;
$argv = substr_replace($argv, $escape, $pos, strlen($b));
$offset += 2;
}
*/
// Find all items, quoted or otherwise
preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
$argv = $av[1];
foreach ($av[2] as $k => $a) {
if (empty($a)) {
continue;
}
$argv[$k] = trim($a) ;
}
}
return Console_Getopt::getopt2($argv, $short_options, $long_options);
}
/**
* Output errors with PHP trigger_error(). You can silence the errors
* with prefixing a "@" sign to the function call: @System::mkdir(..);
*
* @param mixed $error a PEAR error or a string with the error message
* @return bool false
*/
protected static function raiseError($error)
{
if (PEAR::isError($error)) {
$error = $error->getMessage();
}
trigger_error($error, E_USER_WARNING);
return false;
}
/**
* Creates a nested array representing the structure of a directory
*
* System::_dirToStruct('dir1', 0) =>
* Array
* (
* [dirs] => Array
* (
* [0] => dir1
* )
*
* [files] => Array
* (
* [0] => dir1/file2
* [1] => dir1/file3
* )
* )
* @param string $sPath Name of the directory
* @param integer $maxinst max. deep of the lookup
* @param integer $aktinst starting deep of the lookup
* @param bool $silent if true, do not emit errors.
* @return array the structure of the dir
*/
protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
{
$struct = array('dirs' => array(), 'files' => array());
if (($dir = @opendir($sPath)) === false) {
if (!$silent) {
System::raiseError("Could not open dir $sPath");
}
return $struct; // XXX could not open error
}
$struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
$list = array();
while (false !== ($file = readdir($dir))) {
if ($file != '.' && $file != '..') {
$list[] = $file;
}
}
closedir($dir);
natsort($list);
if ($aktinst < $maxinst || $maxinst == 0) {
foreach ($list as $val) {
$path = $sPath . DIRECTORY_SEPARATOR . $val;
if (is_dir($path) && !is_link($path)) {
$tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
$struct = array_merge_recursive($struct, $tmp);
} else {
$struct['files'][] = $path;
}
}
}
return $struct;
}
/**
* Creates a nested array representing the structure of a directory and files
*
* @param array $files Array listing files and dirs
* @return array
* @static
* @see System::_dirToStruct()
*/
protected static function _multipleToStruct($files)
{
$struct = array('dirs' => array(), 'files' => array());
settype($files, 'array');
foreach ($files as $file) {
if (is_dir($file) && !is_link($file)) {
$tmp = System::_dirToStruct($file, 0);
$struct = array_merge_recursive($tmp, $struct);
} else {
if (!in_array($file, $struct['files'])) {
$struct['files'][] = $file;
}
}
}
return $struct;
}
/**
* The rm command for removing files.
* Supports multiple files and dirs and also recursive deletes
*
* @param string $args the arguments for rm
* @return mixed PEAR_Error or true for success
* @static
* @access public
*/
public static function rm($args)
{
$opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'r') {
$do_recursive = true;
}
}
$ret = true;
if (isset($do_recursive)) {
$struct = System::_multipleToStruct($opts[1]);
foreach ($struct['files'] as $file) {
if (!@unlink($file)) {
$ret = false;
}
}
rsort($struct['dirs']);
foreach ($struct['dirs'] as $dir) {
if (!@rmdir($dir)) {
$ret = false;
}
}
} else {
foreach ($opts[1] as $file) {
$delete = (is_dir($file)) ? 'rmdir' : 'unlink';
if (!@$delete($file)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Make directories.
*
* The -p option will create parent directories
* @param string $args the name of the director(y|ies) to create
* @return bool True for success
*/
public static function mkDir($args)
{
$opts = System::_parseArgs($args, 'pm:');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
$mode = 0777; // default mode
foreach ($opts[0] as $opt) {
if ($opt[0] == 'p') {
$create_parents = true;
} elseif ($opt[0] == 'm') {
// if the mode is clearly an octal number (starts with 0)
// convert it to decimal
if (strlen($opt[1]) && $opt[1][0] == '0') {
$opt[1] = octdec($opt[1]);
} else {
// convert to int
$opt[1] += 0;
}
$mode = $opt[1];
}
}
$ret = true;
if (isset($create_parents)) {
foreach ($opts[1] as $dir) {
$dirstack = array();
while ((!file_exists($dir) || !is_dir($dir)) &&
$dir != DIRECTORY_SEPARATOR) {
array_unshift($dirstack, $dir);
$dir = dirname($dir);
}
while ($newdir = array_shift($dirstack)) {
if (!is_writeable(dirname($newdir))) {
$ret = false;
break;
}
if (!mkdir($newdir, $mode)) {
$ret = false;
}
}
}
} else {
foreach($opts[1] as $dir) {
if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Concatenate files
*
* Usage:
* 1) $var = System::cat('sample.txt test.txt');
* 2) System::cat('sample.txt test.txt > final.txt');
* 3) System::cat('sample.txt test.txt >> final.txt');
*
* Note: as the class use fopen, urls should work also
*
* @param string $args the arguments
* @return boolean true on success
*/
public static function &cat($args)
{
$ret = null;
$files = array();
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$count_args = count($args);
for ($i = 0; $i < $count_args; $i++) {
if ($args[$i] == '>') {
$mode = 'wb';
$outputfile = $args[$i+1];
break;
} elseif ($args[$i] == '>>') {
$mode = 'ab+';
$outputfile = $args[$i+1];
break;
} else {
$files[] = $args[$i];
}
}
$outputfd = false;
if (isset($mode)) {
if (!$outputfd = fopen($outputfile, $mode)) {
$err = System::raiseError("Could not open $outputfile");
return $err;
}
$ret = true;
}
foreach ($files as $file) {
if (!$fd = fopen($file, 'r')) {
System::raiseError("Could not open $file");
continue;
}
while ($cont = fread($fd, 2048)) {
if (is_resource($outputfd)) {
fwrite($outputfd, $cont);
} else {
$ret .= $cont;
}
}
fclose($fd);
}
if (is_resource($outputfd)) {
fclose($outputfd);
}
return $ret;
}
/**
* Creates temporary files or directories. This function will remove
* the created files when the scripts finish its execution.
*
* Usage:
* 1) $tempfile = System::mktemp("prefix");
* 2) $tempdir = System::mktemp("-d prefix");
* 3) $tempfile = System::mktemp();
* 4) $tempfile = System::mktemp("-t /var/tmp prefix");
*
* prefix -> The string that will be prepended to the temp name
* (defaults to "tmp").
* -d -> A temporary dir will be created instead of a file.
* -t -> The target dir where the temporary (file|dir) will be created. If
* this param is missing by default the env vars TMP on Windows or
* TMPDIR in Unix will be used. If these vars are also missing
* c:\windows\temp or /tmp will be used.
*
* @param string $args The arguments
* @return mixed the full path of the created (file|dir) or false
* @see System::tmpdir()
*/
public static function mktemp($args = null)
{
static $first_time = true;
$opts = System::_parseArgs($args, 't:d');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'd') {
$tmp_is_dir = true;
} elseif ($opt[0] == 't') {
$tmpdir = $opt[1];
}
}
$prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
if (!isset($tmpdir)) {
$tmpdir = System::tmpdir();
}
if (!System::mkDir(array('-p', $tmpdir))) {
return false;
}
$tmp = tempnam($tmpdir, $prefix);
if (isset($tmp_is_dir)) {
unlink($tmp); // be careful possible race condition here
if (!mkdir($tmp, 0700)) {
return System::raiseError("Unable to create temporary directory $tmpdir");
}
}
$GLOBALS['_System_temp_files'][] = $tmp;
if (isset($tmp_is_dir)) {
//$GLOBALS['_System_temp_files'][] = dirname($tmp);
}
if ($first_time) {
PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
$first_time = false;
}
return $tmp;
}
/**
* Remove temporary files created my mkTemp. This function is executed
* at script shutdown time
*/
public static function _removeTmpFiles()
{
if (count($GLOBALS['_System_temp_files'])) {
$delete = $GLOBALS['_System_temp_files'];
array_unshift($delete, '-r');
System::rm($delete);
$GLOBALS['_System_temp_files'] = array();
}
}
/**
* Get the path of the temporal directory set in the system
* by looking in its environments variables.
* Note: php.ini-recommended removes the "E" from the variables_order setting,
* making unavaible the $_ENV array, that s why we do tests with _ENV
*
* @return string The temporary directory on the system
*/
public static function tmpdir()
{
if (OS_WINDOWS) {
if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
return $var;
}
if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
return $var;
}
if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
return $var;
}
if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
return $var;
}
return getenv('SystemRoot') . '\temp';
}
if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
return $var;
}
return realpath(function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp');
}
/**
* The "which" command (show the full path of a command)
*
* @param string $program The command to search for
* @param mixed $fallback Value to return if $program is not found
*
* @return mixed A string with the full path or false if not found
* @author Stig Bakken <ssb@php.net>
*/
public static function which($program, $fallback = false)
{
// enforce API
if (!is_string($program) || '' == $program) {
return $fallback;
}
// full path given
if (basename($program) != $program) {
$path_elements[] = dirname($program);
$program = basename($program);
} else {
$path = getenv('PATH');
if (!$path) {
$path = getenv('Path'); // some OSes are just stupid enough to do this
}
$path_elements = explode(PATH_SEPARATOR, $path);
}
if (OS_WINDOWS) {
$exe_suffixes = getenv('PATHEXT')
? explode(PATH_SEPARATOR, getenv('PATHEXT'))
: array('.exe','.bat','.cmd','.com');
// allow passing a command.exe param
if (strpos($program, '.') !== false) {
array_unshift($exe_suffixes, '');
}
} else {
$exe_suffixes = array('');
}
foreach ($exe_suffixes as $suff) {
foreach ($path_elements as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
// It's possible to run a .bat on Windows that is_executable
// would return false for. The is_executable check is meaningless...
if (OS_WINDOWS) {
if (file_exists($file)) {
return $file;
}
} else {
if (is_executable($file)) {
return $file;
}
}
}
}
return $fallback;
}
/**
* The "find" command
*
* Usage:
*
* System::find($dir);
* System::find("$dir -type d");
* System::find("$dir -type f");
* System::find("$dir -name *.php");
* System::find("$dir -name *.php -name *.htm*");
* System::find("$dir -maxdepth 1");
*
* Params implemented:
* $dir -> Start the search at this directory
* -type d -> return only directories
* -type f -> return only files
* -maxdepth <n> -> max depth of recursion
* -name <pattern> -> search pattern (bash style). Multiple -name param allowed
*
* @param mixed Either array or string with the command line
* @return array Array of found files
*/
public static function find($args)
{
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$dir = realpath(array_shift($args));
if (!$dir) {
return array();
}
$patterns = array();
$depth = 0;
$do_files = $do_dirs = true;
$args_count = count($args);
for ($i = 0; $i < $args_count; $i++) {
switch ($args[$i]) {
case '-type':
if (in_array($args[$i+1], array('d', 'f'))) {
if ($args[$i+1] == 'd') {
$do_files = false;
} else {
$do_dirs = false;
}
}
$i++;
break;
case '-name':
$name = preg_quote($args[$i+1], '#');
// our magic characters ? and * have just been escaped,
// so now we change the escaped versions to PCRE operators
$name = strtr($name, array('\?' => '.', '\*' => '.*'));
$patterns[] = '('.$name.')';
$i++;
break;
case '-maxdepth':
$depth = $args[$i+1];
break;
}
}
$path = System::_dirToStruct($dir, $depth, 0, true);
if ($do_files && $do_dirs) {
$files = array_merge($path['files'], $path['dirs']);
} elseif ($do_dirs) {
$files = $path['dirs'];
} else {
$files = $path['files'];
}
if (count($patterns)) {
$dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
$pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
$ret = array();
$files_count = count($files);
for ($i = 0; $i < $files_count; $i++) {
// only search in the part of the file below the current directory
$filepart = basename($files[$i]);
if (preg_match($pattern, $filepart)) {
$ret[] = $files[$i];
}
}
return $ret;
}
return $files;
}
}

Some files were not shown because too many files have changed in this diff Show More