framework/Alarm/test/Horde/Alarm/conf.php
framework/Db/test/Horde/Db/Adapter/conf.php
framework/Kolab_Storage/test/Horde/Kolab/Storage/conf.php
+framework/Ldap/test/Horde/Ldap/conf.php
# Dynamically generated content that may live in the repo directories
/lib/
--- /dev/null
+<?php
+/**
+ * Horde_Ldap test suite.
+ *
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_Ldap_AllTests::main');
+}
+
+/**
+ * Prepare the test setup.
+ */
+require_once 'Horde/Test/AllTests.php';
+
+/**
+ * @package Horde_Ldap
+ * @subpackage UnitTests
+ */
+class Horde_Ldap_AllTests extends Horde_Test_AllTests
+{
+}
+
+Horde_Ldap_AllTests::init('Horde_Ldap', __FILE__);
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Ldap_AllTests::main') {
+ Horde_Ldap_AllTests::main();
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_EntryTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testCreateFreshFail()
+ {
+ $entry = Horde_Ldap_Entry::createFresh('cn=test', 'I should be an array');
+ }
+
+ public function testCreateFreshSuccess()
+ {
+ $entry = Horde_Ldap_Entry::createFresh('cn=test',
+ array('attr1' => 'single',
+ 'attr2' => array('mv1', 'mv2')));
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_ExampleTest extends PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ }
+
+ public function testSomething()
+ {
+ }
+
+ public function tearDown()
+ {
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_FilterTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Test correct parsing of filter strings through parse().
+ */
+ public function testParse()
+ {
+ try {
+ Horde_Ldap_Filter::parse('some_damaged_filter_str');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::parse('(invalid=filter)(because=~no-surrounding brackets)');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::parse('((invalid=filter)(because=log_op is missing))');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::parse('(invalid-because-becauseinvalidoperator)');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::parse('(&(filterpart>=ok)(part2=~ok)(filterpart3_notok---becauseinvalidoperator))');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ $parsed1 = Horde_Ldap_Filter::parse('(&(cn=foo)(ou=bar))');
+ $this->assertType('Horde_Ldap_Filter', $parsed1);
+ $this->assertEquals('(&(cn=foo)(ou=bar))', (string)$parsed1);
+
+ // In an earlier version there was a problem with the splitting of the
+ // filter parts if the next part was also an combined filter.
+ $parsed2_str = '(&(&(objectClass=posixgroup)(objectClass=foogroup))(uniquemember=uid=eeggs,ou=people,o=foo))';
+ $parsed2 = Horde_Ldap_Filter::parse($parsed2_str);
+ $this->assertType('Horde_Ldap_Filter', $parsed2);
+ $this->assertEquals($parsed2_str, (string)$parsed2);
+
+ // In an earlier version there was a problem parsing certain
+ // not-combined filter strings.
+ $parsed3_str = '(!(jpegPhoto=*))';
+ $parsed3 = Horde_Ldap_Filter::parse($parsed3_str);
+ $this->assertType('Horde_Ldap_Filter', $parsed3);
+ $this->assertEquals($parsed3_str, (string)$parsed3);
+
+ $parsed3_complex_str = '(&(someAttr=someValue)(!(jpegPhoto=*)))';
+ $parsed3_complex = Horde_Ldap_Filter::parse($parsed3_complex_str);
+ $this->assertType('Horde_Ldap_Filter', $parsed3_complex);
+ $this->assertEquals($parsed3_complex_str, (string)$parsed3_complex);
+ }
+
+ /**
+ * This tests the basic create() method of creating filters.
+ */
+ public function testCreate()
+ {
+ // Test values and an array containing the filter creating methods and
+ // an regex to test the resulting filter.
+ $testattr = 'testattr';
+ $testval = 'testval';
+ $combinations = array(
+ 'equals' => "/\($testattr=$testval\)/",
+ 'begins' => "/\($testattr=$testval\*\)/",
+ 'ends' => "/\($testattr=\*$testval\)/",
+ 'contains' => "/\($testattr=\*$testval\*\)/",
+ 'greater' => "/\($testattr>$testval\)/",
+ 'less' => "/\($testattr<$testval\)/",
+ 'greaterorequal' => "/\($testattr>=$testval\)/",
+ 'lessorequal' => "/\($testattr<=$testval\)/",
+ 'approx' => "/\($testattr~=$testval\)/",
+ 'any' => "/\($testattr=\*\)/"
+ );
+
+ foreach ($combinations as $match => $regex) {
+ // Escaping is tested in util class.
+ $filter = Horde_Ldap_Filter::create($testattr, $match, $testval, false);
+ $this->assertType('Horde_Ldap_Filter', $filter);
+ $this->assertRegExp($regex, (string)$filter, "Filter generation failed for MatchType: $match");
+ }
+
+ // Test creating failure.
+ try {
+ Horde_Ldap_Filter::create($testattr, 'test_undefined_matchingrule', $testval);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Tests if __toString() works.
+ */
+ public function testToString()
+ {
+ $filter = Horde_Ldap_Filter::create('foo', 'equals', 'bar');
+ $this->assertType('Horde_Ldap_Filter', $filter);
+ $this->assertEquals('(foo=bar)', (string)$filter);
+ }
+
+ /**
+ * This tests the basic combination of filters.
+ */
+ public function testCombine()
+ {
+ // Setup.
+ $filter0 = Horde_Ldap_Filter::create('foo', 'equals', 'bar');
+ $this->assertType('Horde_Ldap_Filter', $filter0);
+
+ $filter1 = Horde_Ldap_Filter::create('bar', 'equals', 'foo');
+ $this->assertType('Horde_Ldap_Filter', $filter1);
+
+ $filter2 = Horde_Ldap_Filter::create('you', 'equals', 'me');
+ $this->assertType('Horde_Ldap_Filter', $filter2);
+
+ $filter3 = Horde_Ldap_Filter::parse('(perlinterface=used)');
+ $this->assertType('Horde_Ldap_Filter', $filter3);
+
+ // Negation test.
+ $filter_not1 = Horde_Ldap_Filter::combine('not', $filter0);
+ $this->assertType('Horde_Ldap_Filter', $filter_not1, 'Negation failed for literal NOT');
+ $this->assertEquals('(!(foo=bar))', (string)$filter_not1);
+
+ $filter_not2 = Horde_Ldap_Filter::combine('!', $filter0);
+ $this->assertType('Horde_Ldap_Filter', $filter_not2, 'Negation failed for logical NOT');
+ $this->assertEquals('(!(foo=bar))', (string)$filter_not2);
+
+ $filter_not3 = Horde_Ldap_Filter::combine('!', (string)$filter0);
+ $this->assertType('Horde_Ldap_Filter', $filter_not3, 'Negation failed for logical NOT');
+ $this->assertEquals('(!' . $filter0 . ')', (string)$filter_not3);
+
+ // Combination test: OR
+ $filter_comb_or1 = Horde_Ldap_Filter::combine('or', array($filter1, $filter2));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_or1, 'Combination failed for literal OR');
+ $this->assertEquals('(|(bar=foo)(you=me))', (string)$filter_comb_or1);
+
+ $filter_comb_or2 = Horde_Ldap_Filter::combine('|', array($filter1, $filter2));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_or2, 'combination failed for logical OR');
+ $this->assertEquals('(|(bar=foo)(you=me))', (string)$filter_comb_or2);
+
+ // Combination test: AND
+ $filter_comb_and1 = Horde_Ldap_Filter::combine('and', array($filter1, $filter2));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_and1, 'Combination failed for literal AND');
+ $this->assertEquals('(&(bar=foo)(you=me))', (string)$filter_comb_and1);
+
+ $filter_comb_and2 = Horde_Ldap_Filter::combine('&', array($filter1, $filter2));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_and2, 'combination failed for logical AND');
+ $this->assertEquals('(&(bar=foo)(you=me))', (string)$filter_comb_and2);
+
+ // Combination test: using filter created with perl interface.
+ $filter_comb_perl1 = Horde_Ldap_Filter::combine('and', array($filter1, $filter3));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_perl1, 'Combination failed for literal AND');
+ $this->assertEquals('(&(bar=foo)(perlinterface=used))', (string)$filter_comb_perl1);
+
+ $filter_comb_perl2 = Horde_Ldap_Filter::combine('&', array($filter1, $filter3));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_perl2, 'combination failed for logical AND');
+ $this->assertEquals('(&(bar=foo)(perlinterface=used))', (string)$filter_comb_perl2);
+
+ // Combination test: using filter_str instead of object
+ $filter_comb_fstr1 = Horde_Ldap_Filter::combine('and', array($filter1, '(filter_str=foo)'));
+ $this->assertType('Horde_Ldap_Filter', $filter_comb_fstr1, 'Combination failed for literal AND using filter_str');
+ $this->assertEquals('(&(bar=foo)(filter_str=foo))', (string)$filter_comb_fstr1);
+
+ // Combination test: deep combination
+ $filter_comp_deep = Horde_Ldap_Filter::combine('and',array($filter2, $filter_not1, $filter_comb_or1, $filter_comb_perl1));
+ $this->assertType('Horde_Ldap_Filter', $filter_comp_deep, 'Deep combination failed!');
+ $this->assertEquals('(&(you=me)(!(foo=bar))(|(bar=foo)(you=me))(&(bar=foo)(perlinterface=used)))', (string)$filter_comp_deep);
+
+ // Test failure in combination
+ try {
+ Horde_Ldap_Filter::create('foo', 'test_undefined_matchingrule', 'bar');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('not', 'damaged_filter_str');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('not', array($filter0, $filter1));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('not', null);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('and', $filter_not1);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('and', array($filter_not1));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('and', $filter_not1);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('or', array($filter_not1));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('some_unknown_method', array($filter_not1));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('and', array($filter_not1, 'some_invalid_filterstring'));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ try {
+ Horde_Ldap_Filter::combine('and', array($filter_not1, null));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+}
--- /dev/null
+<?php
+
+require_once dirname(__FILE__) . '/TestBase.php';
+
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_LdapTest extends Horde_Ldap_TestBase
+{
+ public static function tearDownAfterClass()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $clean = array('cn=Horde_Ldap_TestEntry,',
+ 'ou=Horde_Ldap_Test_subdelete,',
+ 'ou=Horde_Ldap_Test_modify,',
+ 'ou=Horde_Ldap_Test_search1,',
+ 'ou=Horde_Ldap_Test_search2,',
+ 'ou=Horde_Ldap_Test_exists,',
+ 'ou=Horde_Ldap_Test_getEntry,',
+ 'ou=Horde_Ldap_Test_move,',
+ 'ou=Horde_Ldap_Test_pool,',
+ 'ou=Horde_Ldap_Test_tgt,');
+ foreach ($clean as $dn) {
+ try {
+ $ldap->delete($dn . self::$ldapcfg['server']['basedn'], true);
+ } catch (Exception $e) {}
+ }
+ }
+
+ /**
+ * Tests if the server can connect and bind correctly.
+ */
+ public function testConnectAndPrivilegedBind()
+ {
+ // This connect is supposed to fail.
+ $lcfg = array('host' => 'nonexistant.ldap.horde.org');
+ try {
+ $ldap = new Horde_Ldap($lcfg);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Failing with multiple hosts.
+ $lcfg = array('host' => array('nonexistant1.ldap.horde.org',
+ 'nonexistant2.ldap.horde.org'));
+ try {
+ $ldap = new Horde_Ldap($lcfg);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Simple working connect and privilegued bind.
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Working connect and privileged bind with first host down.
+ $lcfg = array('host' => array('nonexistant.ldap.horde.org',
+ self::$ldapcfg['server']['host']),
+ 'port' => self::$ldapcfg['server']['port'],
+ 'binddn' => self::$ldapcfg['server']['binddn'],
+ 'bindpw' => self::$ldapcfg['server']['bindpw']);
+ $ldap = new Horde_Ldap($lcfg);
+ }
+
+ /**
+ * Tests if the server can connect and bind anonymously, if supported.
+ */
+ public function testConnectAndAnonymousBind()
+ {
+ if (!self::$ldapcfg['capability']['anonymous']) {
+ $this->markTestSkipped('Server does not support anonymous bind');
+ }
+
+ // Simple working connect and anonymous bind.
+ $lcfg = array('host' => self::$ldapcfg['server']['host'],
+ 'port' => self::$ldapcfg['server']['port']);
+ $ldap = new Horde_Ldap($lcfg);
+ }
+
+ /**
+ * Tests startTLS() if server supports it.
+ */
+ public function testStartTLS()
+ {
+ if (!self::$ldapcfg['capability']['tls']) {
+ $this->markTestSkipped('Server does not support TLS');
+ }
+
+ // Simple working connect and privileged bind.
+ $lcfg = array('starttls' => true) + self::$ldapcfg['server'];
+ $ldap = new Horde_Ldap($lcfg);
+ }
+
+ /**
+ * Test if adding and deleting a fresh entry works.
+ */
+ public function testAdd()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Adding a fresh entry.
+ $cn = 'Horde_Ldap_TestEntry';
+ $dn = 'cn=' . $cn . ',' . self::$ldapcfg['server']['basedn'];
+ $fresh_entry = Horde_Ldap_Entry::createFresh(
+ $dn,
+ array('objectClass' => array('top', 'person'),
+ 'cn' => $cn,
+ 'sn' => 'TestEntry'));
+ $this->assertType('Horde_Ldap_Entry', $fresh_entry);
+ $ldap->add($fresh_entry);
+
+ // Deleting this entry.
+ $ldap->delete($fresh_entry);
+ }
+
+ /**
+ * Basic deletion is tested in testAdd(), so here we just test if
+ * advanced deletion tasks work properly.
+ */
+ public function testDelete()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Some parameter checks.
+ try {
+ $ldap->delete(1234);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ try {
+ $ldap->delete($ldap);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // In order to test subtree deletion, we need some little tree
+ // which we need to establish first.
+ $base = self::$ldapcfg['server']['basedn'];
+ $testdn = 'ou=Horde_Ldap_Test_subdelete,' . $base;
+
+ $ou = Horde_Ldap_Entry::createFresh(
+ $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_subdelete'));
+ $ou_1 = Horde_Ldap_Entry::createFresh(
+ 'ou=test1,' . $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'test1'));
+ $ou_1_l1 = Horde_Ldap_Entry::createFresh(
+ 'l=subtest,ou=test1,' . $testdn,
+ array('objectClass' => array('top', 'locality'),
+ 'l' => 'test1'));
+ $ou_2 = Horde_Ldap_Entry::createFresh(
+ 'ou=test2,' . $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'test2'));
+ $ou_3 = Horde_Ldap_Entry::createFresh(
+ 'ou=test3,' . $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'test3'));
+ $ldap->add($ou);
+ $ldap->add($ou_1);
+ $ldap->add($ou_1_l1);
+ $ldap->add($ou_2);
+ $ldap->add($ou_3);
+ $this->assertTrue($ldap->exists($ou->dn()));
+ $this->assertTrue($ldap->exists($ou_1->dn()));
+ $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+ $this->assertTrue($ldap->exists($ou_2->dn()));
+ $this->assertTrue($ldap->exists($ou_3->dn()));
+ // Tree established now. We can run some tests now :D
+
+ // Try to delete some non existent entry inside that subtree (fails).
+ try {
+ $ldap->delete('cn=not_existent,ou=test1,' . $testdn);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {
+ $this->assertEquals('LDAP_NO_SUCH_OBJECT', Horde_Ldap::errorName($e->getCode()));
+ }
+
+ // Try to delete main test ou without recursive set (fails too).
+ try {
+ $ldap->delete($testdn);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {
+ $this->assertEquals('LDAP_NOT_ALLOWED_ON_NONLEAF', Horde_Ldap::errorName($e->getCode()));
+ }
+
+ // Retry with subtree delete, this should work.
+ $ldap->delete($testdn, true);
+
+ // The DN is not allowed to exist anymore.
+ $this->assertFalse($ldap->exists($testdn));
+ }
+
+ /**
+ * Test modify().
+ */
+ public function testModify()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // We need a test entry.
+ $local_entry = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_modify,' . self::$ldapcfg['server']['basedn'],
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_modify',
+ 'street' => 'Beniroad',
+ 'telephoneNumber' => array('1234', '5678'),
+ 'postalcode' => '12345',
+ 'postalAddress' => 'someAddress',
+ 'facsimileTelephoneNumber' => array('123', '456')));
+ $ldap->add($local_entry);
+ $this->assertTrue($ldap->exists($local_entry->dn()));
+
+ // Prepare some changes.
+ $changes = array(
+ 'add' => array(
+ 'businessCategory' => array('foocat', 'barcat'),
+ 'description' => 'testval'
+ ),
+ 'delete' => array('postalAddress'),
+ 'replace' => array('telephoneNumber' => array('345', '567')),
+ 'changes' => array(
+ 'replace' => array('street' => 'Highway to Hell'),
+ 'add' => array('l' => 'someLocality'),
+ 'delete' => array(
+ 'postalcode',
+ 'facsimileTelephoneNumber' => array('123'))));
+
+ // Perform those changes.
+ $ldap->modify($local_entry, $changes);
+
+ // Verify correct attribute changes.
+ $actual_entry = $ldap->getEntry($local_entry->dn(),
+ array('objectClass', 'ou',
+ 'postalAddress', 'street',
+ 'telephoneNumber', 'postalcode',
+ 'facsimileTelephoneNumber', 'l',
+ 'businessCategory', 'description'));
+ $this->assertType('Horde_Ldap_Entry', $actual_entry);
+ $expected_attributes = array(
+ 'objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_modify',
+ 'street' => 'Highway to Hell',
+ 'l' => 'someLocality',
+ 'telephoneNumber' => array('345', '567'),
+ 'businessCategory' => array('foocat', 'barcat'),
+ 'description' => 'testval',
+ 'facsimileTelephoneNumber' => '456'
+ );
+
+ $local_attributes = $local_entry->getValues();
+ $actual_attributes = $actual_entry->getValues();
+
+ // To enable easy check, we need to sort the values of the remaining
+ // multival attributes as well as the attribute names.
+ ksort($expected_attributes);
+ ksort($local_attributes);
+ ksort($actual_attributes);
+ sort($expected_attributes['businessCategory']);
+ sort($local_attributes['businessCategory']);
+ sort($actual_attributes['businessCategory']);
+
+ // The attributes must match the expected values. Both, the entry
+ // inside the directory and our local copy must reflect the same
+ // values.
+ $this->assertEquals($expected_attributes, $actual_attributes, 'The directory entries attributes are not OK!');
+ $this->assertEquals($expected_attributes, $local_attributes, 'The local entries attributes are not OK!');
+ }
+
+ /**
+ * Test search().
+ */
+ public function testSearch()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Some testdata, so we can test sizelimit.
+ $base = self::$ldapcfg['server']['basedn'];
+ $ou1 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_search1,' . $base,
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search1'));
+ $ou1_1 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_search1_1,' . $ou1->dn(),
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search2'));
+ $ou2 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_search2,' . $base,
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search2'));
+ $ldap->add($ou1);
+ $this->assertTrue($ldap->exists($ou1->dn()));
+ $ldap->add($ou1_1);
+ $this->assertTrue($ldap->exists($ou1_1->dn()));
+ $ldap->add($ou2);
+ $this->assertTrue($ldap->exists($ou2->dn()));
+
+
+ // Search for test filter, should at least return our two test entries.
+ $res = $ldap->search(null, '(ou=Horde_Ldap*)',
+ array('attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
+
+ // Same, but with Horde_Ldap_Filter object.
+ $filtero = Horde_Ldap_Filter::create('ou', 'begins', 'Horde_Ldap');
+ $this->assertType('Horde_Ldap_Filter', $filtero);
+ $res = $ldap->search(null, $filtero,
+ array('attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
+
+ // Search using default filter for base-onelevel scope, should at least
+ // return our two test entries.
+ $res = $ldap->search(null, null,
+ array('scope' => 'one', 'attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
+
+ // Base-search using custom base (string), should only return the test
+ // entry $ou1 and not the entry below it.
+ $res = $ldap->search($ou1->dn(), null,
+ array('scope' => 'base', 'attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertEquals(1, $res->count());
+
+ // Search using custom base, this time using an entry object. This
+ // tests if passing an entry object as base works, should only return
+ // the test entry $ou1.
+ $res = $ldap->search($ou1, '(ou=*)',
+ array('scope' => 'base', 'attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertEquals(1, $res->count());
+
+ // Search using default filter for base-onelevel scope with sizelimit,
+ // should of course return more than one entry, but not more than
+ // sizelimit
+ $res = $ldap->search(
+ null, null,
+ array('scope' => 'one', 'sizelimit' => 1, 'attributes' => '1.1')
+ );
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertEquals(1, $res->count());
+ // Sizelimit should be exceeded now.
+ $this->assertTrue($res->sizeLimitExceeded());
+
+ // Bad filter.
+ try {
+ $res = $ldap->search(null, 'somebadfilter',
+ array('attributes' => '1.1'));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Bad base.
+ try {
+ $res = $ldap->search('badbase', null,
+ array('attributes' => '1.1'));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Nullresult.
+ $res = $ldap->search(null, '(cn=nevermatching_filter)',
+ array('scope' => 'base', 'attributes' => '1.1'));
+ $this->assertType('Horde_Ldap_Search', $res);
+ $this->assertEquals(0, $res->count());
+ }
+
+ /**
+ * Test exists().
+ */
+ public function testExists()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ $dn = 'ou=Horde_Ldap_Test_exists,' . self::$ldapcfg['server']['basedn'];
+
+ // Testing not existing DN.
+ $this->assertFalse($ldap->exists($dn));
+
+ // Passing an entry object (should work). It should return false,
+ // because we didn't add the test entry yet.
+ $ou1 = Horde_Ldap_Entry::createFresh(
+ $dn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search1'));
+ $this->assertFalse($ldap->exists($ou1));
+
+ // Testing not existing DN.
+ $ldap->add($ou1);
+ $this->assertTrue($ldap->exists($dn));
+
+ // Passing an float instead of a string.
+ try {
+ $ldap->exists(1.234);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Test getEntry().
+ */
+ public function testGetEntry()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $dn = 'ou=Horde_Ldap_Test_getEntry,' . self::$ldapcfg['server']['basedn'];
+ $entry = Horde_Ldap_Entry::createFresh(
+ $dn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_getEntry'));
+ $ldap->add($entry);
+
+ // Existing DN.
+ $this->assertType('Horde_Ldap_Entry', $ldap->getEntry($dn));
+
+ // Not existing DN.
+ try {
+ $ldap->getEntry('cn=notexistent,' . self::$ldapcfg['server']['basedn']);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Test move().
+ */
+ public function testMove()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // For Moving tests, we need some little tree again.
+ $base = self::$ldapcfg['server']['basedn'];
+ $testdn = 'ou=Horde_Ldap_Test_move,' . $base;
+
+ $ou = Horde_Ldap_Entry::createFresh(
+ $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_move'));
+ $ou_1 = Horde_Ldap_Entry::createFresh(
+ 'ou=source,' . $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'source'));
+ $ou_1_l1 = Horde_Ldap_Entry::createFresh(
+ 'l=moveitem,ou=source,' . $testdn,
+ array('objectClass' => array('top','locality'),
+ 'l' => 'moveitem',
+ 'description' => 'movetest'));
+ $ou_2 = Horde_Ldap_Entry::createFresh(
+ 'ou=target,' . $testdn,
+ array('objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'target'));
+ $ou_3 = Horde_Ldap_Entry::createFresh(
+ 'ou=target_otherdir,' . $testdn,
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'target_otherdir'));
+ $ldap->add($ou);
+ $ldap->add($ou_1);
+ $ldap->add($ou_1_l1);
+ $ldap->add($ou_2);
+ $ldap->add($ou_3);
+ $this->assertTrue($ldap->exists($ou->dn()));
+ $this->assertTrue($ldap->exists($ou_1->dn()));
+ $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+ $this->assertTrue($ldap->exists($ou_2->dn()));
+ $this->assertTrue($ldap->exists($ou_3->dn()));
+ // Tree established.
+
+ // Local rename.
+ $olddn = $ou_1_l1->currentDN();
+ $ldap->move($ou_1_l1, str_replace('moveitem', 'move_item', $ou_1_l1->dn()));
+ $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+ $this->assertFalse($ldap->exists($olddn));
+
+ // Local move.
+ $olddn = $ou_1_l1->currentDN();
+ $ldap->move($ou_1_l1, 'l=move_item,' . $ou_2->dn());
+ $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+ $this->assertFalse($ldap->exists($olddn));
+
+ // Local move backward, with rename. Here we use the DN of the object,
+ // to test DN conversion.
+ // Note that this will outdate the object since it does not has
+ // knowledge about the move.
+ $olddn = $ou_1_l1->currentDN();
+ $newdn = 'l=moveditem,' . $ou_2->dn();
+ $ldap->move($olddn, $newdn);
+ $this->assertTrue($ldap->exists($newdn));
+ $this->assertFalse($ldap->exists($olddn));
+ // Refetch since the object's DN was outdated.
+ $ou_1_l1 = $ldap->getEntry($newdn);
+
+ // Fake-cross directory move using two separate links to the same
+ // directory. This other directory is represented by
+ // ou=target_otherdir.
+ $ldap2 = new Horde_Ldap(self::$ldapcfg['server']);
+ $olddn = $ou_1_l1->currentDN();
+ $ldap->move($ou_1_l1, 'l=movedcrossdir,' . $ou_3->dn(), $ldap2);
+ $this->assertFalse($ldap->exists($olddn));
+ $this->assertTrue($ldap2->exists($ou_1_l1->dn()));
+
+ // Try to move over an existing entry.
+ try {
+ $ldap->move($ou_2, $ou_3->dn(), $ldap2);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Try cross directory move without providing an valid entry but a DN.
+ try {
+ $ldap->move($ou_1_l1->dn(), 'l=movedcrossdir2,'.$ou_2->dn(), $ldap2);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Try passing an invalid entry object.
+ try {
+ $ldap->move($ldap, 'l=move_item,'.$ou_2->dn());
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Try passing an invalid LDAP object.
+ try {
+ $ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn(), $ou_1);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Test copy().
+ */
+ public function testCopy()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Some testdata.
+ $base = self::$ldapcfg['server']['basedn'];
+ $ou1 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_pool,' . $base,
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_copy'));
+ $ou2 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_tgt,' . $base,
+ array('objectClass' => array('top','organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_copy'));
+ $ldap->add($ou1);
+ $this->assertTrue($ldap->exists($ou1->dn()));
+ $ldap->add($ou2);
+ $this->assertTrue($ldap->exists($ou2->dn()));
+
+ $entry = Horde_Ldap_Entry::createFresh(
+ 'l=cptest,' . $ou1->dn(),
+ array('objectClass' => array('top','locality'),
+ 'l' => 'cptest'));
+ $ldap->add($entry);
+ $ldap->exists($entry->dn());
+
+ // Copy over the entry to another tree with rename.
+ $entrycp = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
+ $this->assertType('Horde_Ldap_Entry', $entrycp);
+ $this->assertNotEquals($entry->dn(), $entrycp->dn());
+ $this->assertTrue($ldap->exists($entrycp->dn()));
+
+ // Copy same again (fails, entry exists).
+ try {
+ $entrycp_f = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Use only DNs to copy (fails).
+ try {
+ $entrycp = $ldap->copy($entry->dn(), 'l=test_copied2,' . $ou2->dn());
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Tests retrieval of root DSE object.
+ */
+ public function testRootDSE()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $this->assertType('Horde_Ldap_RootDse', $ldap->rootDSE());
+ }
+
+ /**
+ * Tests retrieval of schema through LDAP object.
+ */
+ public function testSchema()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $this->assertType('Horde_Ldap_Schema', $ldap->schema());
+ }
+
+ /**
+ * Test getLink().
+ */
+ public function testGetLink()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $this->assertTrue(is_resource($ldap->getLink()));
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_LdifTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Default configuration for tests.
+ *
+ * The config is bound to the ldif test file
+ * tests/fixtures/unsorted_w50.ldif, so don't change or tests will fail.
+ *
+ * @var array
+ */
+ protected $_defaultConfig = array(
+ 'encode' => 'base64',
+ 'wrap' => 50,
+ 'change' => 0,
+ 'sort' => 0,
+ 'version' => 1
+ );
+
+ /**
+ * Test entries data.
+ *
+ * Please do not just modify these values, they are closely related to the
+ * LDIF test data.
+ *
+ * @var array
+ */
+ protected $_testdata = array(
+ 'cn=test1,ou=example,dc=cno' => array(
+ 'cn' => 'test1',
+ 'attr3' => array('foo', 'bar'),
+ 'attr1' => 12345,
+ 'attr4' => 'brrrzztt',
+ 'objectclass' => 'oc1',
+ 'attr2' => array('1234', 'baz')),
+
+ 'cn=test blabla,ou=example,dc=cno' => array(
+ 'cn' => 'test blabla',
+ 'attr3' => array('foo', 'bar'),
+ 'attr1' => 12345,
+ 'attr4' => 'blablaöäü',
+ 'objectclass' => 'oc2',
+ 'attr2' => array('1234', 'baz'),
+ 'verylong' => 'fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738'),
+
+ 'cn=test öäü,ou=example,dc=cno' => array(
+ 'cn' => 'test öäü',
+ 'attr3' => array('foo', 'bar'),
+ 'attr1' => 12345,
+ 'attr4' => 'blablaöäü',
+ 'objectclass' => 'oc3',
+ 'attr2' => array('1234', 'baz'),
+ 'attr5' => 'endspace ',
+ 'attr6' => ':badinitchar'),
+
+ ':cn=endspace,dc=cno ' => array(
+ 'cn' => 'endspace')
+ );
+
+ /**
+ * Test file written to.
+ *
+ * @var string
+ */
+ protected $_outfile = 'test.out.ldif';
+
+ /**
+ * Test entries.
+ *
+ * They will be created in setUp()
+ *
+ * @var array
+ */
+ protected $_testentries;
+
+ /**
+ * Opens an outfile and ensures correct permissions.
+ */
+ public function setUp()
+ {
+ // Initialize test entries.
+ $this->_testentries = array();
+ foreach ($this->_testdata as $dn => $attrs) {
+ $entry = Horde_Ldap_Entry::createFresh($dn, $attrs);
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ array_push($this->_testentries, $entry);
+ }
+
+ // Create outfile if not exists and enforce proper access rights.
+ if (!file_exists($this->_outfile)) {
+ if (!touch($this->_outfile)) {
+ $this->markTestSkipped('Unable to create ' . $this->_outfile);
+ }
+ }
+ if (!chmod($this->_outfile, 0644)) {
+ $this->markTestSkipped('Unable to chmod(0644) ' . $this->_outfile);
+ }
+ }
+
+ /**
+ * Removes the outfile.
+ */
+ public function tearDown() {
+ @unlink($this->_outfile);
+ }
+
+ /**
+ * Construction tests.
+ *
+ * Construct LDIF object and see if we can get a handle.
+ */
+ public function testConstruction()
+ {
+ $supported_modes = array('r', 'w', 'a');
+ $plus = array('', '+');
+
+ // Test all open modes, all of them should return a correct handle.
+ foreach ($supported_modes as $mode) {
+ foreach ($plus as $p) {
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, $mode, $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+ }
+ }
+
+ // Test illegal option passing.
+ try {
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, $mode, array('somebad' => 'option'));
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Test passing custom handle.
+ $handle = fopen($this->_outfile, 'r');
+ $ldif = new Horde_Ldap_Ldif($handle, $mode, $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+
+ // Reading test with invalid file mode.
+ try {
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'y', $this->_defaultConfig);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Reading test with non-existent file.
+ try {
+ $ldif = new Horde_Ldap_Ldif('some/nonexistent/file_for_net_ldap_ldif', 'r', $this->_defaultConfig);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Writing to non-existent file.
+ $ldif = new Horde_Ldap_Ldif('testfile_for_net_ldap_ldif', 'w', $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+ @unlink('testfile_for_net_ldap_ldif');
+
+ // Writing to non-existent path.
+ try {
+ $ldif = new Horde_Ldap_Ldif('some/nonexistent/file_for_net_ldap_ldif', 'w', $this->_defaultConfig);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+
+ // Writing to existing file but without permission. chmod() should
+ // succeed since we test that in setUp().
+ if (chmod($this->_outfile, 0444)) {
+ try {
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $this->_defaultConfig);
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ } else {
+ $this->markTestSkipped('Could not chmod ' . $this->_outfile . ', write test without permission skipped');
+ }
+ }
+
+ /**
+ * Tests if entries from an LDIF file are correctly constructed.
+ */
+ public function testReadEntry()
+ {
+ /* UNIX line endings. */
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ array_push($entries, $entry);
+ } while (!$ldif->eof());
+
+ $this->_compareEntries($this->_testentries, $entries);
+
+ /* Windows line endings. */
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50_WIN.ldif', 'r', $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ array_push($entries, $entry);
+ } while (!$ldif->eof());
+
+ $this->_compareEntries($this->_testentries, $entries);
+ }
+
+ /**
+ * Tests if entries are correctly written.
+ *
+ * This tests converting entries to LDIF lines, wrapping, encoding, etc.
+ */
+ public function testWriteEntry()
+ {
+ $testconf = $this->_defaultConfig;
+
+ /* Test wrapped operation. */
+ $testconf['wrap'] = 50;
+ $testconf['sort'] = 0;
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files.
+ $this->assertEquals($expected, file($this->_outfile));
+
+ $testconf['wrap'] = 30;
+ $testconf['sort'] = 0;
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w30.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files.
+ $this->assertEquals($expected, file($this->_outfile));
+
+ /* Test unwrapped operation. */
+ $testconf['wrap'] = 40;
+ $testconf['sort'] = 1;
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w40.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files.
+ $this->assertEquals($expected, file($this->_outfile));
+
+ $testconf['wrap'] = 50;
+ $testconf['sort'] = 1;
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w50.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files.
+ $this->assertEquals($expected, file($this->_outfile));
+
+ /* Test raw option. */
+ $testconf['wrap'] = 50;
+ $testconf['sort'] = 1;
+ $testconf['raw'] = '/attr6/';
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w50.ldif'));
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files, with expected attributes adjusted.
+ $this->assertEquals($expected, file($this->_outfile));
+
+ /* Test writing with non entry as parameter. */
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w');
+ $this->assertTrue(is_resource($ldif->handle()));
+ try {
+ $ldif->writeEntry('malformed_parameter');
+ $this->fail('Horde_Ldap_Exception expected.');
+ } catch (Horde_Ldap_Exception $e) {}
+ }
+
+ /**
+ * Test version writing.
+ */
+ public function testWriteVersion()
+ {
+ $testconf = $this->_defaultConfig;
+
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ // Strip 1 additional line (the "version: 1" line that should not be
+ // written now) and adjust test config.
+ array_shift($expected);
+ unset($testconf['version']);
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($this->_testentries);
+ $ldif->done();
+
+ // Compare files.
+ $this->assertEquals($expected, file($this->_outfile));
+ }
+
+ /**
+ * Round trip test: Read LDIF, parse to entries, write that to LDIF and
+ * compare both files.
+ */
+ public function testReadWriteRead()
+ {
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+
+ // Read LDIF.
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ array_push($entries, $entry);
+ } while (!$ldif->eof());
+ $ldif->done();
+
+ // Write LDIF.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $this->_defaultConfig);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($entries);
+ $ldif->done();
+
+ // Compare files.
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ $this->assertEquals($expected, file($this->_outfile));
+ }
+
+ /**
+ * Tests if entry changes are correctly written.
+ */
+ public function testWriteEntryChanges()
+ {
+ $testentries = $this->_testentries;
+ $testentries[] = Horde_Ldap_Entry::createFresh('cn=foo,ou=example,dc=cno', array('cn' => 'foo'));
+ $testentries[] = Horde_Ldap_Entry::createFresh('cn=footest,ou=example,dc=cno', array('cn' => 'foo'));
+
+ $testconf = $this->_defaultConfig;
+ $testconf['change'] = 1;
+
+ /* No changes should produce empty file. */
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($testentries);
+ $ldif->done();
+ $this->assertEquals(array(), file($this->_outfile));
+
+ /* Changes test. */
+ // Prepare some changes.
+ $testentries[0]->delete('attr1');
+ $testentries[0]->delete(array('attr2' => 'baz'));
+ $testentries[0]->delete(array('attr4', 'attr3' => 'bar'));
+
+ // Prepare some replaces and adds.
+ $testentries[2]->replace(array('attr1' => 'newvaluefor1'));
+ $testentries[2]->replace(array('attr2' => array('newvalue1for2', 'newvalue2for2')));
+ $testentries[2]->replace(array('attr3' => ''));
+ $testentries[2]->replace(array('newattr' => 'foo'));
+
+ // Delete whole entry.
+ $testentries[3]->delete();
+
+ // Rename and move.
+ $testentries[4]->dn('cn=Bar,ou=example,dc=cno');
+ $testentries[5]->dn('cn=foobartest,ou=newexample,dc=cno');
+
+ // Carry out write.
+ $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+ $this->assertTrue(is_resource($ldif->handle()));
+ $ldif->writeEntry($testentries);
+ $ldif->done();
+
+ // Compare results.
+ $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/changes.ldif'));
+
+ // Strip 4 starting lines because of comments in the file header.
+ array_splice($expected, 0, 4);
+
+ $this->assertEquals($expected, file($this->_outfile));
+ }
+
+ /**
+ * Tests if syntax errors are detected.
+ *
+ * The used LDIF files have several damaged entries but always one
+ * correct too, to test if Horde_Ldap_Ldif is continue reading as it should
+ * each entry must have 2 correct attributes.
+ */
+ public function testSyntaxerrors()
+ {
+ $this->markTestSkipped('We don\'t continue on syntax errors.');
+ // Test malformed encoding
+ // I think we can ignore this test, because if the LDIF is not encoded properly, we
+ // might be able to successfully fetch the entries data. However, it is possible
+ // that it will be corrupted, but thats not our fault then.
+ // If we should catch that error, we must adjust Horde_Ldap_Ldif::next_lines().
+ /*
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_encoding.ldif', 'r', $this->_defaultConfig);
+ $this->assertFalse((boolean)$ldif->error());
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ if ($entry) {
+ // the correct attributes need to be parsed
+ $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+ $entries[] = $entry;
+ }
+ } while (!$ldif->eof());
+ $this->assertTrue((boolean)$ldif->error());
+ $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+ $this->assertThat(count($entries), $this->equalTo(1));
+ */
+
+ // Test malformed syntax
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_syntax.ldif', 'r', $this->_defaultConfig);
+ $this->assertFalse((boolean)$ldif->error());
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ if ($entry) {
+ // the correct attributes need to be parsed
+ $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+ $entries[] = $entry;
+ }
+ } while (!$ldif->eof());
+ $this->assertTrue((boolean)$ldif->error());
+ $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+ $this->assertThat(count($entries), $this->equalTo(2));
+
+ // test bad wrapping
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_wrapping.ldif', 'r', $this->_defaultConfig);
+ $this->assertFalse((boolean)$ldif->error());
+ $entries = array();
+ do {
+ $entry = $ldif->readEntry();
+ if ($entry) {
+ // the correct attributes need to be parsed
+ $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+ $entries[] = $entry;
+ }
+ } while (!$ldif->eof());
+ $this->assertTrue((boolean)$ldif->error());
+ $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+ $this->assertThat(count($entries), $this->equalTo(2));
+ }
+
+ /**
+ * Test error dropping functionality.
+ */
+ public function testError()
+ {
+ $this->markTestSkipped('We use exceptions, not the original error handling.');
+
+ // No error.
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+
+ // Error giving error msg and line number:
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/some_not_existing/path/for/net_ldap_ldif', 'r', $this->_defaultConfig);
+ $this->assertTrue((boolean)$ldif->error());
+ $this->assertType('Net_LDAP2_Error', $ldif->error());
+ $this->assertType('string', $ldif->error(true));
+ $this->assertType('int', $ldif->error_lines());
+ $this->assertThat(strlen($ldif->error(true)), $this->greaterThan(0));
+
+ // Test for line number reporting
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_syntax.ldif', 'r', $this->_defaultConfig);
+ $this->assertFalse((boolean)$ldif->error());
+ do { $entry = $ldif->readEntry(); } while (!$ldif->eof());
+ $this->assertTrue((boolean)$ldif->error());
+ $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+ }
+
+ /**
+ * Tests currentLines() and nextLines().
+ *
+ * This should always return the same lines unless forced.
+ */
+ public function testLineMethods()
+ {
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+ $this->assertEquals(array(), $ldif->currentLines(), 'Horde_Ldap_Ldif initialization error!');
+
+ // Read first lines.
+ $lines = $ldif->nextLines();
+
+ // Read the first lines several times and test.
+ for ($i = 0; $i <= 10; $i++) {
+ $r_lines = $ldif->nextLines();
+ $this->assertEquals($lines, $r_lines);
+ }
+
+ // Now force to iterate and see if the content changes.
+ $r_lines = $ldif->nextLines(true);
+ $this->assertNotEquals($lines, $r_lines);
+
+ // It could be confusing to some people, but calling currentEntry()
+ // would not work now, like the description of the method says.
+ $no_entry = $ldif->currentLines();
+ $this->assertEquals(array(), $no_entry);
+ }
+
+ /**
+ * Tests currentEntry(). This should always return the same object.
+ */
+ public function testcurrentEntry()
+ {
+ $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+
+ // Read first entry.
+ $entry = $ldif->readEntry();
+
+ // Test if currentEntry remains the first one.
+ for ($i = 0; $i <= 10; $i++) {
+ $e = $ldif->currentEntry();
+ $this->assertEquals($entry, $e);
+ }
+ }
+
+ /**
+ * Compares two Horde_Ldap_Entries.
+ *
+ * This helper function compares two entries (or array of entries) and
+ * checks if they are equal. They are equal if all DNs from the first crowd
+ * exist in the second AND each attribute is present and equal at the
+ * respective entry. The search is case sensitive.
+ *
+ * @param array|Horde_Ldap_Entry $entry1
+ * @param array|Horde_Ldap_Entry $entry2
+ * @return boolean
+ */
+ protected function _compareEntries($entry1, $entry2)
+ {
+ if (!is_array($entry1)) {
+ $entry1 = array($entry1);
+ }
+ if (!is_array($entry2)) {
+ $entry2 = array($entry2);
+ }
+
+ $entries_data1 = $entries_data2 = array();
+
+ // Step 1: extract and sort data.
+ foreach ($entry1 as $e) {
+ $values = $e->getValues();
+ foreach ($values as $attr_name => $attr_values) {
+ if (!is_array($attr_values)) {
+ $attr_values = array($attr_values);
+ }
+ $values[$attr_name] = $attr_values;
+ }
+ $entries_data1[$e->dn()] = $values;
+ }
+ foreach ($entry2 as $e) {
+ $values = $e->getValues();
+ foreach ($values as $attr_name => $attr_values) {
+ if (!is_array($attr_values)) {
+ $attr_values = array($attr_values);
+ }
+ $values[$attr_name] = $attr_values;
+ }
+ $entries_data2[$e->dn()] = $values;
+ }
+
+ // Step 2: compare DNs (entries).
+ $this->assertEquals(array_keys($entries_data1), array_keys($entries_data2), 'Entries DNs not equal! (missing entry or wrong DN)');
+
+ // Step 3: look for attribute existence and compare values.
+ foreach ($entries_data1 as $dn => $attributes) {
+ $this->assertEquals($entries_data1[$dn], $entries_data2[$dn], 'Entries ' . $dn . ' attributes are not equal');
+ foreach ($attributes as $attr_name => $attr_values) {
+ $this->assertEquals(0, count(array_diff($entries_data1[$dn][$attr_name], $entries_data2[$dn][$attr_name])), 'Entries ' . $dn . ' attribute ' . $attr_name . ' values are not equal');
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create line endings for current OS.
+ *
+ * This is neccessary to make write tests platform indendent.
+ *
+ * @param string $line Line
+ * @return string
+ */
+ protected function _lineend($line)
+ {
+ return rtrim($line) . PHP_EOL;
+ }
+}
--- /dev/null
+<?php
+
+require_once dirname(__FILE__) . '/TestBase.php';
+
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_SearchTest extends Horde_Ldap_TestBase
+{
+ public static function tearDownAfterClass()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+ $ldap->delete('ou=Horde_Ldap_Test_search1,' . self::$ldapcfg['server']['basedn']);
+ $ldap->delete('ou=Horde_Ldap_Test_search2,' . self::$ldapcfg['server']['basedn']);
+ }
+
+ /**
+ * Tests SPL iterator.
+ */
+ public function testSPLIterator()
+ {
+ $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+ // Some testdata, so we have some entries to search for.
+ $base = self::$ldapcfg['server']['basedn'];
+ $ou1 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_search1,' . $base,
+ array(
+ 'objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search1'));
+ $ou2 = Horde_Ldap_Entry::createFresh(
+ 'ou=Horde_Ldap_Test_search2,' . $base,
+ array(
+ 'objectClass' => array('top', 'organizationalUnit'),
+ 'ou' => 'Horde_Ldap_Test_search2'));
+
+ $ldap->add($ou1);
+ $this->assertTrue($ldap->exists($ou1->dn()));
+ $ldap->add($ou2);
+ $this->assertTrue($ldap->exists($ou2->dn()));
+
+ /* Search and test each method. */
+ $search = $ldap->search(null, '(ou=Horde_Ldap*)');
+ $this->assertType('Horde_Ldap_Search', $search);
+ $this->assertEquals(2, $search->count());
+
+ // current() is supposed to return first valid element.
+ $e1 = $search->current();
+ $this->assertType('Horde_Ldap_Entry', $e1);
+ $this->assertEquals($e1->dn(), $search->key());
+ $this->assertTrue($search->valid());
+
+ // Shift to next entry.
+ $search->next();
+ $e2 = $search->current();
+ $this->assertType('Horde_Ldap_Entry', $e2);
+ $this->assertEquals($e2->dn(), $search->key());
+ $this->assertTrue($search->valid());
+
+ // Shift to non existent third entry.
+ $search->next();
+ $this->assertFalse($search->current());
+ $this->assertFalse($search->key());
+ $this->assertFalse($search->valid());
+
+ // Rewind and test, which should return the first entry a second time.
+ $search->rewind();
+ $e1_1 = $search->current();
+ $this->assertType('Horde_Ldap_Entry', $e1_1);
+ $this->assertEquals($e1_1->dn(), $search->key());
+ $this->assertTrue($search->valid());
+ $this->assertEquals($e1->dn(), $e1_1->dn());
+
+ // Don't rewind but call current, should return first entry again.
+ $e1_2 = $search->current();
+ $this->assertType('Horde_Ldap_Entry', $e1_2);
+ $this->assertEquals($e1_2->dn(), $search->key());
+ $this->assertTrue($search->valid());
+ $this->assertEquals($e1->dn(), $e1_2->dn());
+
+ // Rewind again and test, which should return the first entry a third
+ // time.
+ $search->rewind();
+ $e1_3 = $search->current();
+ $this->assertType('Horde_Ldap_Entry', $e1_3);
+ $this->assertEquals($e1_3->dn(), $search->key());
+ $this->assertTrue($search->valid());
+ $this->assertEquals($e1->dn(), $e1_3->dn());
+
+ /* Try methods on empty search result. */
+ $search = $ldap->search(null, '(ou=Horde_LdapTest_NotExistentEntry)');
+ $this->assertType('Horde_Ldap_Search', $search);
+ $this->assertEquals(0, $search->count());
+ $this->assertFalse($search->current());
+ $this->assertFalse($search->key());
+ $this->assertFalse($search->valid());
+ $search->next();
+ $this->assertFalse($search->current());
+ $this->assertFalse($search->key());
+ $this->assertFalse($search->valid());
+
+ /* Search and simple iterate through the test entries. Then, rewind
+ * and do it again several times. */
+ $search2 = $ldap->search(null, '(ou=Horde_Ldap*)');
+ $this->assertType('Horde_Ldap_Search', $search2);
+ $this->assertEquals(2, $search2->count());
+ for ($i = 0; $i <= 5; $i++) {
+ $counter = 0;
+ foreach ($search2 as $dn => $entry) {
+ $counter++;
+ // Check on type.
+ $this->assertType('Horde_Ldap_Entry', $entry);
+ // Check on key.
+ $this->assertThat(strlen($dn), $this->greaterThan(1));
+ $this->assertEquals($dn, $entry->dn());
+ }
+ $this->assertEquals($search2->count(), $counter, "Failed at loop $i");
+
+ // Revert to start.
+ $search2->rewind();
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_TestBase extends PHPUnit_Framework_TestCase
+{
+ protected static $ldapcfg;
+
+ public function setUp()
+ {
+ // Check extension.
+ try {
+ Horde_Ldap::checkLDAPExtension();
+ } catch (Horde_Ldap_Exception $e) {
+ $this->markTestSkipped($e->getMessage());
+ }
+
+ $file = dirname(__FILE__) . '/conf.php';
+ if (!file_exists($file) || !is_readable($file)) {
+ $this->markTestSkipped('conf.php cannot be opened.');
+ }
+ include $file;
+ self::$ldapcfg = $conf;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Ldap
+ * @subpackage UnitTests
+ * @author Jan Schneider <jan@horde.org>
+ * @copyright 2010 The Horde Project
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_UtilTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Test escape_dn_value()
+ */
+ public function testEscape_dn_value()
+ {
+ $dnval = ' ' . chr(22) . ' t,e+s"t,\\v<a>l;u#e=! ';
+ $expected = '\20\20\16 t\,e\+s\"t\,\\\\v\<a\>l\;u\#e\=!\20\20\20\20';
+
+ // String call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::escape_dn_value($dnval));
+
+ // Array call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::escape_dn_value(array($dnval)));
+
+ // Multiple arrays.
+ $this->assertEquals(
+ array($expected, $expected, $expected),
+ Horde_Ldap_Util::escape_dn_value(array($dnval, $dnval, $dnval)));
+ }
+
+ /**
+ * Test unescape_dn_value()
+ */
+ public function testUnescape_dn_value()
+ {
+ $dnval = '\\20\\20\\16\\20t\\,e\\+s \\"t\\,\\\\v\\<a\\>l\\;u\\#e\\=!\\20\\20\\20\\20';
+ $expected = ' ' . chr(22) . ' t,e+s "t,\\v<a>l;u#e=! ';
+
+ // String call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::unescape_dn_value($dnval));
+
+ // Array call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::unescape_dn_value(array($dnval)));
+
+ // Multiple arrays.
+ $this->assertEquals(
+ array($expected, $expected, $expected),
+ Horde_Ldap_Util::unescape_dn_value(array($dnval, $dnval, $dnval)));
+ }
+
+ /**
+ * Test escaping of filter values.
+ */
+ public function testEscape_filter_value()
+ {
+ $expected = 't\28e,s\29t\2av\5cal\1eue';
+ $filterval = 't(e,s)t*v\\al' . chr(30) . 'ue';
+
+ // String call
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::escape_filter_value($filterval));
+
+ // Array call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::escape_filter_value(array($filterval)));
+
+ // Multiple arrays.
+ $this->assertEquals(
+ array($expected, $expected, $expected),
+ Horde_Ldap_Util::escape_filter_value(array($filterval, $filterval, $filterval)));
+ }
+
+ /**
+ * Test unescaping of filter values.
+ */
+ public function testUnescape_filter_value()
+ {
+ $expected = 't(e,s)t*v\\al' . chr(30) . 'ue';
+ $filterval = 't\28e,s\29t\2av\5cal\1eue';
+
+ // String call
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::unescape_filter_value($filterval));
+
+ // Array call.
+ $this->assertEquals(
+ array($expected),
+ Horde_Ldap_Util::unescape_filter_value(array($filterval)));
+
+ // Multiple arrays.
+ $this->assertEquals(
+ array($expected, $expected, $expected),
+ Horde_Ldap_Util::unescape_filter_value(array($filterval, $filterval, $filterval)));
+ }
+
+ /**
+ * Test asc2hex32()
+ */
+ public function testAsc2hex32()
+ {
+ $expected = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+ $str = '';
+ for ($i = 0; $i < 127; $i++) {
+ $str .= chr($i);
+ }
+ $this->assertEquals($expected, Horde_Ldap_Util::asc2hex32($str));
+ }
+
+ /**
+ * Test HEX unescaping
+ */
+ public function testHex2asc()
+ {
+ $expected = '';
+ for ($i = 0; $i < 127; $i++) {
+ $expected .= chr($i);
+ }
+ $str = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+ $this->assertEquals($expected, Horde_Ldap_Util::hex2asc($str));
+ }
+
+ /**
+ * Tests split_rdn_multival()
+ *
+ * In addition to the above test of the basic split correction, we test
+ * here the functionality of multivalued RDNs.
+ */
+ public function testSplit_rdn_multival()
+ {
+ // One value.
+ $rdn = 'CN=J. Smith';
+ $expected = array('CN=J. Smith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Two values.
+ $rdn = 'OU=Sales+CN=J. Smith';
+ $expected = array('OU=Sales', 'CN=J. Smith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Several multivals.
+ $rdn = 'OU=Sales+CN=J. Smith+L=London+C=England';
+ $expected = array('OU=Sales', 'CN=J. Smith', 'L=London', 'C=England');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Unescaped "+" in value.
+ $rdn = 'OU=Sa+les+CN=J. Smith';
+ $expected = array('OU=Sa+les', 'CN=J. Smith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Unescaped "+" in attr name.
+ $rdn = 'O+U=Sales+CN=J. Smith';
+ $expected = array('O+U=Sales', 'CN=J. Smith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Unescaped "+" in attr name + value.
+ $rdn = 'O+U=Sales+CN=J. Sm+ith';
+ $expected = array('O+U=Sales', 'CN=J. Sm+ith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Unescaped "+" in attribute name, but not first attribute. This
+ // documents a known bug. However, unfortunately we can't know wether
+ // the "C+" belongs to value "Sales" or attribute "C+N". To solve
+ // this, we must ask the schema which we do not right now. The problem
+ // is located in _correct_dn_splitting().
+ $rdn = 'OU=Sales+C+N=J. Smith';
+ // The "C+" is treaten as value of "OU".
+ $expected = array('OU=Sales+C', 'N=J. Smith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+
+ // Escaped "+" in attribute name and value.
+ $rdn = 'O\+U=Sales+CN=J. Sm\+ith';
+ $expected = array('O\+U=Sales', 'CN=J. Sm\+ith');
+ $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+ $this->assertEquals($expected, $split);
+ }
+
+ /**
+ * Tests attribute splitting ('foo=bar' => array('foo', 'bar'))
+ */
+ public function testSplit_attribute_string()
+ {
+ $attr_str = 'foo=bar';
+
+ // Properly.
+ $expected = array('foo', 'bar');
+ $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+ $this->assertEquals($expected, $split);
+
+ // Escaped "=".
+ $attr_str = "fo\=o=b\=ar";
+ $expected = array('fo\=o', 'b\=ar');
+ $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+ $this->assertEquals($expected, $split);
+
+ // Escaped "=" and unescaped = later on.
+ $attr_str = "fo\=o=b=ar";
+ $expected = array('fo\=o', 'b=ar');
+ $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+ $this->assertEquals($expected, $split);
+ }
+
+ /**
+ * Tests Ldap_explode_dn()
+ */
+ public function testLdap_explode_dn()
+ {
+ $dn = 'OU=Sales+CN=J. Smith,dc=example,dc=net';
+ $expected_casefold_none = array(
+ array('CN=J. Smith', 'OU=Sales'),
+ 'dc=example',
+ 'dc=net'
+ );
+ $expected_casefold_upper = array(
+ array('CN=J. Smith', 'OU=Sales'),
+ 'DC=example',
+ 'DC=net'
+ );
+ $expected_casefold_lower = array(
+ array('cn=J. Smith', 'ou=Sales'),
+ 'dc=example',
+ 'dc=net'
+ );
+ $expected_onlyvalues = array(
+ array( 'J. Smith', 'Sales'),
+ 'example',
+ 'net'
+ );
+ $expected_reverse = array_reverse($expected_casefold_upper);
+
+
+ $dn_exploded_cnone = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'none'));
+ $this->assertEquals($expected_casefold_none, $dn_exploded_cnone, 'Option casefold none failed');
+
+ $dn_exploded_cupper = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'upper'));
+ $this->assertEquals($expected_casefold_upper, $dn_exploded_cupper, 'Option casefold upper failed');
+
+ $dn_exploded_clower = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'lower'));
+ $this->assertEquals($expected_casefold_lower, $dn_exploded_clower, 'Option casefold lower failed');
+
+ $dn_exploded_onlyval = Horde_Ldap_Util::ldap_explode_dn($dn, array('onlyvalues' => true));
+ $this->assertEquals($expected_onlyvalues, $dn_exploded_onlyval, 'Option onlyval failed');
+
+ $dn_exploded_reverse = Horde_Ldap_Util::ldap_explode_dn($dn, array('reverse' => true));
+ $this->assertEquals($expected_reverse, $dn_exploded_reverse, 'Option reverse failed');
+ }
+
+ /**
+ * Tests if canonical_dn() works.
+ *
+ * Note: This tests depend on the default options of canonical_dn().
+ */
+ public function testCanonical_dn()
+ {
+ // Test empty dn (is valid according to RFC).
+ $this->assertEquals('', Horde_Ldap_Util::canonical_dn(''));
+
+ // Default options with common DN.
+ $testdn = 'cn=beni,DC=php,c=net';
+ $expected = 'CN=beni,DC=php,C=net';
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+ // Casefold tests with common DN.
+ $expected_up = 'CN=beni,DC=php,C=net';
+ $expected_lo = 'cn=beni,dc=php,c=net';
+ $expected_no = 'cn=beni,DC=php,c=net';
+ $this->assertEquals($expected_up, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'upper')));
+ $this->assertEquals($expected_lo, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'lower')));
+ $this->assertEquals($expected_no, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'none')));
+
+ // Reverse.
+ $expected_rev = 'C=net,DC=php,CN=beni';
+ $this->assertEquals($expected_rev, Horde_Ldap_Util::canonical_dn($testdn, array('reverse' => true)), 'Option reverse failed');
+
+ // DN as arrays.
+ $dn_index = array('cn=beni', 'dc=php', 'c=net');
+ $dn_assoc = array('cn' => 'beni', 'dc' => 'php', 'c' => 'net');
+ $expected = 'CN=beni,DC=php,C=net';
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($dn_index));
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($dn_assoc));
+
+ // DN with multiple RDN value.
+ $testdn = 'ou=dev+cn=beni,DC=php,c=net';
+ $testdn_index = array(array('ou=dev', 'cn=beni'), 'DC=php', 'c=net');
+ $testdn_assoc = array(array('ou' => 'dev', 'cn' => 'beni'), 'DC' => 'php', 'c' => 'net');
+ $expected = 'CN=beni+OU=dev,DC=php,C=net';
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn_assoc));
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($expected));
+
+ // Test DN with OID.
+ $testdn = 'OID.2.5.4.3=beni,dc=php,c=net';
+ $expected = '2.5.4.3=beni,DC=php,C=net';
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+ // Test with leading and ending spaces.
+ $testdn = 'cn= beni ,DC=php,c=net';
+ $expected = 'CN=\20\20beni\20\20,DC=php,C=net';
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+ // Test with to-be escaped characters in attribute value.
+ $specialchars = array(
+ ',' => '\,',
+ '+' => '\+',
+ '"' => '\"',
+ '\\' => '\\\\',
+ '<' => '\<',
+ '>' => '\>',
+ ';' => '\;',
+ '#' => '\#',
+ '=' => '\=',
+ chr(18) => '\12',
+ '/' => '\/'
+ );
+ foreach ($specialchars as $char => $escape) {
+ $test_string = 'CN=be' . $char . 'ni,DC=ph' . $char . 'p,C=net';
+ $test_index = array('CN=be' . $char . 'ni', 'DC=ph' . $char . 'p', 'C=net');
+ $test_assoc = array('CN' => 'be' . $char . 'ni', 'DC' => 'ph' . $char . 'p', 'C' => 'net');
+ $expected = 'CN=be' . $escape . 'ni,DC=ph' . $escape . 'p,C=net';
+
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_string), 'String escaping test (' . $char . ') failed');
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_index), 'Indexed array escaping test (' . $char . ') failed');
+ $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_assoc), 'Associative array encoding test (' . $char . ') failed');
+ }
+ }
+}
--- /dev/null
+<?php
+$conf = array(
+ 'server' => array(
+ 'host' => 'localhost',
+ 'port' => 389,
+ 'basedn' => 'ou=hordetest,dc=example,dc=com',
+ 'writedn' => 'cn=admin,dc=example,dc=com',
+ 'writepw' => 'secret'),
+ 'capability' => array(
+ 'anonymous' => true,
+ 'tls' => true),
+);
--- /dev/null
+#
+# This is a LDIF file to test writing changes of entries
+#
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+changetype: modify
+delete: attr1
+-
+delete: attr2
+attr2: baz
+-
+delete: attr4
+-
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+changetype: modify
+add: newattr
+newattr: foo
+-
+delete: attr3
+-
+replace: attr1
+attr1: newvaluefor1
+-
+replace: attr2
+attr2: newvalue1for2
+attr2: newvalue2for2
+-
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+changetype: delete
+
+dn: cn=foo,ou=example,dc=cno
+changetype: modrdn
+newrdn: cn=Bar
+deleteoldrdn: 1
+
+dn: cn=footest,ou=example,dc=cno
+changetype: modrdn
+newrdn: cn=foobartest
+deleteoldrdn: 1
+newsuperior: ou=newexample,dc=cno
--- /dev/null
+#
+# This is a LDIF file to test encoding failure
+#
+
+# unencoded DN
+version: 1
+dn: cn=testöäü,ou=example,dc=cno
+objectclass: oc1
+
+# unencoded attr value
+version: 1
+dn: cn=test2,ou=example,dc=cno
+objectclass: testöäü
+cn: test2
+
+# entry ok
+version: 1
+dn: cn=test,ou=example,dc=cno
+objectclass: oc1
--- /dev/null
+#
+# This is a LDIF file to test syntax error
+#
+
+# wrong syntax (space too less at val of objectclass)
+dn: cn=test1,ou=example,dc=cno
+objectclass:oc1
+cn: test1
+attr3: foo
+
+# wrong syntax (no DN given)
+objectclass:oc1
+cn: test_invalid
+attr3: foo
+
+# entry ok
+version: 1
+dn: cn=test3,ou=example,dc=cno
+objectclass: oc1
+attr3: foo
--- /dev/null
+#
+# This is a LDIF file to test wrapping failure
+#
+
+# wrong wrapping (entry must fail because DN is damaged):
+# (note, that there must eb an empty line below this comment, otherwise
+# the DN line is treaten as wrapped comment)
+
+ dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+cn: test1
+
+# wrong syntax (literal line but no wrapped content)
+dn: cn=test2,ou=example,dc=cno
+objectclass:oc1
+cn: test2
+some_wrong_literal_line
+attr3: foo
+
+# entry ok
+version: 1
+dn: cn=test,ou=example,dc=cno
+objectclass: oc1
+cn: test
--- /dev/null
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=1, wrap=40
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4: brrrzztt
+cn: test1
+
+dn: cn=test blabla,ou=example,dc=cno
+objectclass: oc2
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+cn: test blabla
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+objectclass: oc3
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+cn:: dGVzdCD25Pw=
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
--- /dev/null
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=1, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4: brrrzztt
+cn: test1
+
+dn: cn=test blabla,ou=example,dc=cno
+objectclass: oc2
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+cn: test blabla
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+objectclass: oc3
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+cn:: dGVzdCD25Pw=
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
--- /dev/null
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=0, wrap=30
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
--- /dev/null
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=0, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
--- /dev/null
+#
+# This is a LDIF file to test reading capabilitys with WINDOWS line endings
+# It was created using options: sort=0, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace