LDIF.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. /**
  4. * File containing the Net_LDAP2_LDIF interface class.
  5. *
  6. * PHP version 5
  7. *
  8. * @category Net
  9. * @package Net_LDAP2
  10. * @author Benedikt Hallinger <beni@php.net>
  11. * @copyright 2009 Benedikt Hallinger
  12. * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
  13. * @version SVN: $Id: LDIF.php 324918 2012-04-06 12:31:04Z clockwerx $
  14. * @link http://pear.php.net/package/Net_LDAP2/
  15. */
  16. /**
  17. * Includes
  18. */
  19. require_once 'PEAR.php';
  20. require_once 'Net/LDAP2.php';
  21. require_once 'Net/LDAP2/Entry.php';
  22. require_once 'Net/LDAP2/Util.php';
  23. /**
  24. * LDIF capabilitys for Net_LDAP2, closely taken from PERLs Net::LDAP
  25. *
  26. * It provides a means to convert between Net_LDAP2_Entry objects and LDAP entries
  27. * represented in LDIF format files. Reading and writing are supported and may
  28. * manipulate single entries or lists of entries.
  29. *
  30. * Usage example:
  31. * <code>
  32. * // Read and parse an ldif-file into Net_LDAP2_Entry objects
  33. * // and print out the DNs. Store the entries for later use.
  34. * require 'Net/LDAP2/LDIF.php';
  35. * $options = array(
  36. * 'onerror' => 'die'
  37. * );
  38. * $entries = array();
  39. * $ldif = new Net_LDAP2_LDIF('test.ldif', 'r', $options);
  40. * do {
  41. * $entry = $ldif->read_entry();
  42. * $dn = $entry->dn();
  43. * echo " done building entry: $dn\n";
  44. * array_push($entries, $entry);
  45. * } while (!$ldif->eof());
  46. * $ldif->done();
  47. *
  48. *
  49. * // write those entries to another file
  50. * $ldif = new Net_LDAP2_LDIF('test.out.ldif', 'w', $options);
  51. * $ldif->write_entry($entries);
  52. * $ldif->done();
  53. * </code>
  54. *
  55. * @category Net
  56. * @package Net_LDAP2
  57. * @author Benedikt Hallinger <beni@php.net>
  58. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  59. * @link http://pear.php.net/package/Net_LDAP22/
  60. * @see http://www.ietf.org/rfc/rfc2849.txt
  61. * @todo Error handling should be PEARified
  62. * @todo LDAPv3 controls are not implemented yet
  63. */
  64. class Net_LDAP2_LDIF extends PEAR
  65. {
  66. /**
  67. * Options
  68. *
  69. * @access protected
  70. * @var array
  71. */
  72. protected $_options = array('encode' => 'base64',
  73. 'onerror' => null,
  74. 'change' => 0,
  75. 'lowercase' => 0,
  76. 'sort' => 0,
  77. 'version' => null,
  78. 'wrap' => 78,
  79. 'raw' => ''
  80. );
  81. /**
  82. * Errorcache
  83. *
  84. * @access protected
  85. * @var array
  86. */
  87. protected $_error = array('error' => null,
  88. 'line' => 0
  89. );
  90. /**
  91. * Filehandle for read/write
  92. *
  93. * @access protected
  94. * @var array
  95. */
  96. protected $_FH = null;
  97. /**
  98. * Says, if we opened the filehandle ourselves
  99. *
  100. * @access protected
  101. * @var array
  102. */
  103. protected $_FH_opened = false;
  104. /**
  105. * Linecounter for input file handle
  106. *
  107. * @access protected
  108. * @var array
  109. */
  110. protected $_input_line = 0;
  111. /**
  112. * counter for processed entries
  113. *
  114. * @access protected
  115. * @var int
  116. */
  117. protected $_entrynum = 0;
  118. /**
  119. * Mode we are working in
  120. *
  121. * Either 'r', 'a' or 'w'
  122. *
  123. * @access protected
  124. * @var string
  125. */
  126. protected $_mode = false;
  127. /**
  128. * Tells, if the LDIF version string was already written
  129. *
  130. * @access protected
  131. * @var boolean
  132. */
  133. protected $_version_written = false;
  134. /**
  135. * Cache for lines that have build the current entry
  136. *
  137. * @access protected
  138. * @var boolean
  139. */
  140. protected $_lines_cur = array();
  141. /**
  142. * Cache for lines that will build the next entry
  143. *
  144. * @access protected
  145. * @var boolean
  146. */
  147. protected $_lines_next = array();
  148. /**
  149. * Open LDIF file for reading or for writing
  150. *
  151. * new (FILE):
  152. * Open the file read-only. FILE may be the name of a file
  153. * or an already open filehandle.
  154. * If the file doesn't exist, it will be created if in write mode.
  155. *
  156. * new (FILE, MODE, OPTIONS):
  157. * Open the file with the given MODE (see PHPs fopen()), eg "w" or "a".
  158. * FILE may be the name of a file or an already open filehandle.
  159. * PERLs Net_LDAP2 "FILE|" mode does not work curently.
  160. *
  161. * OPTIONS is an associative array and may contain:
  162. * encode => 'none' | 'canonical' | 'base64'
  163. * Some DN values in LDIF cannot be written verbatim and have to be encoded in some way:
  164. * 'none' No encoding.
  165. * 'canonical' See "canonical_dn()" in Net::LDAP::Util.
  166. * 'base64' Use base64. (default, this differs from the Perl interface.
  167. * The perl default is "none"!)
  168. *
  169. * onerror => 'die' | 'warn' | NULL
  170. * Specify what happens when an error is detected.
  171. * 'die' Net_LDAP2_LDIF will croak with an appropriate message.
  172. * 'warn' Net_LDAP2_LDIF will warn (echo) with an appropriate message.
  173. * NULL Net_LDAP2_LDIF will not warn (default), use error().
  174. *
  175. * change => 1
  176. * Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP
  177. * operations acting on the entries to the file instead of the entries contents.
  178. * This writes the changes usually carried out by an update() to the LDIF file.
  179. *
  180. * lowercase => 1
  181. * Convert attribute names to lowercase when writing.
  182. *
  183. * sort => 1
  184. * Sort attribute names when writing entries according to the rule:
  185. * objectclass first then all other attributes alphabetically sorted by attribute name
  186. *
  187. * version => '1'
  188. * Set the LDIF version to write to the resulting LDIF file.
  189. * According to RFC 2849 currently the only legal value for this option is 1.
  190. * When this option is set Net_LDAP2_LDIF tries to adhere more strictly to
  191. * the LDIF specification in RFC2489 in a few places.
  192. * The default is NULL meaning no version information is written to the LDIF file.
  193. *
  194. * wrap => 78
  195. * Number of columns where output line wrapping shall occur.
  196. * Default is 78. Setting it to 40 or lower inhibits wrapping.
  197. *
  198. * raw => REGEX
  199. * Use REGEX to denote the names of attributes that are to be
  200. * considered binary in search results if writing entries.
  201. * Example: raw => "/(?i:^jpegPhoto|;binary)/i"
  202. *
  203. * @param string|ressource $file Filename or filehandle
  204. * @param string $mode Mode to open filename
  205. * @param array $options Options like described above
  206. */
  207. public function __construct($file, $mode = 'r', $options = array())
  208. {
  209. $this->PEAR('Net_LDAP2_Error'); // default error class
  210. // First, parse options
  211. // todo: maybe implement further checks on possible values
  212. foreach ($options as $option => $value) {
  213. if (!array_key_exists($option, $this->_options)) {
  214. $this->dropError('Net_LDAP2_LDIF error: option '.$option.' not known!');
  215. return;
  216. } else {
  217. $this->_options[$option] = strtolower($value);
  218. }
  219. }
  220. // setup LDIF class
  221. $this->version($this->_options['version']);
  222. // setup file mode
  223. if (!preg_match('/^[rwa]\+?$/', $mode)) {
  224. $this->dropError('Net_LDAP2_LDIF error: file mode '.$mode.' not supported!');
  225. } else {
  226. $this->_mode = $mode;
  227. // setup filehandle
  228. if (is_resource($file)) {
  229. // TODO: checks on mode possible?
  230. $this->_FH =& $file;
  231. } else {
  232. $imode = substr($this->_mode, 0, 1);
  233. if ($imode == 'r') {
  234. if (!file_exists($file)) {
  235. $this->dropError('Unable to open '.$file.' for read: file not found');
  236. $this->_mode = false;
  237. }
  238. if (!is_readable($file)) {
  239. $this->dropError('Unable to open '.$file.' for read: permission denied');
  240. $this->_mode = false;
  241. }
  242. }
  243. if (($imode == 'w' || $imode == 'a')) {
  244. if (file_exists($file)) {
  245. if (!is_writable($file)) {
  246. $this->dropError('Unable to open '.$file.' for write: permission denied');
  247. $this->_mode = false;
  248. }
  249. } else {
  250. if (!@touch($file)) {
  251. $this->dropError('Unable to create '.$file.' for write: permission denied');
  252. $this->_mode = false;
  253. }
  254. }
  255. }
  256. if ($this->_mode) {
  257. $this->_FH = @fopen($file, $this->_mode);
  258. if (false === $this->_FH) {
  259. // Fallback; should never be reached if tests above are good enough!
  260. $this->dropError('Net_LDAP2_LDIF error: Could not open file '.$file);
  261. } else {
  262. $this->_FH_opened = true;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. /**
  269. * Read one entry from the file and return it as a Net::LDAP::Entry object.
  270. *
  271. * @return Net_LDAP2_Entry
  272. */
  273. public function read_entry()
  274. {
  275. // read fresh lines, set them as current lines and create the entry
  276. $attrs = $this->next_lines(true);
  277. if (count($attrs) > 0) {
  278. $this->_lines_cur = $attrs;
  279. }
  280. return $this->current_entry();
  281. }
  282. /**
  283. * Returns true when the end of the file is reached.
  284. *
  285. * @return boolean
  286. */
  287. public function eof()
  288. {
  289. return feof($this->_FH);
  290. }
  291. /**
  292. * Write the entry or entries to the LDIF file.
  293. *
  294. * If you want to build an LDIF file containing several entries AND
  295. * you want to call write_entry() several times, you must open the filehandle
  296. * in append mode ("a"), otherwise you will always get the last entry only.
  297. *
  298. * @param Net_LDAP2_Entry|array $entries Entry or array of entries
  299. *
  300. * @return void
  301. * @todo implement operations on whole entries (adding a whole entry)
  302. */
  303. public function write_entry($entries)
  304. {
  305. if (!is_array($entries)) {
  306. $entries = array($entries);
  307. }
  308. foreach ($entries as $entry) {
  309. $this->_entrynum++;
  310. if (!$entry instanceof Net_LDAP2_Entry) {
  311. $this->dropError('Net_LDAP2_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP2_Entry object');
  312. } else {
  313. if ($this->_options['change']) {
  314. // LDIF change mode
  315. // fetch change information from entry
  316. $entry_attrs_changes = $entry->getChanges();
  317. $num_of_changes = count($entry_attrs_changes['add'])
  318. + count($entry_attrs_changes['replace'])
  319. + count($entry_attrs_changes['delete']);
  320. $is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
  321. // write version if not done yet
  322. // also write DN of entry
  323. if ($is_changed) {
  324. if (!$this->_version_written) {
  325. $this->write_version();
  326. }
  327. $this->writeDN($entry->currentDN());
  328. }
  329. // process changes
  330. // TODO: consider DN add!
  331. if ($entry->willBeDeleted()) {
  332. $this->writeLine("changetype: delete".PHP_EOL);
  333. } elseif ($entry->willBeMoved()) {
  334. $this->writeLine("changetype: modrdn".PHP_EOL);
  335. $olddn = Net_LDAP2_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
  336. $oldrdn = array_shift($olddn);
  337. $oldparent = implode(',', $olddn);
  338. $newdn = Net_LDAP2_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
  339. $rdn = array_shift($newdn);
  340. $parent = implode(',', $newdn);
  341. $this->writeLine("newrdn: ".$rdn.PHP_EOL);
  342. $this->writeLine("deleteoldrdn: 1".PHP_EOL);
  343. if ($parent !== $oldparent) {
  344. $this->writeLine("newsuperior: ".$parent.PHP_EOL);
  345. }
  346. // TODO: What if the entry has attribute changes as well?
  347. // I think we should check for that and make a dummy
  348. // entry with the changes that is written to the LDIF file
  349. } elseif ($num_of_changes > 0) {
  350. // write attribute change data
  351. $this->writeLine("changetype: modify".PHP_EOL);
  352. foreach ($entry_attrs_changes as $changetype => $entry_attrs) {
  353. foreach ($entry_attrs as $attr_name => $attr_values) {
  354. $this->writeLine("$changetype: $attr_name".PHP_EOL);
  355. if ($attr_values !== null) $this->writeAttribute($attr_name, $attr_values, $changetype);
  356. $this->writeLine("-".PHP_EOL);
  357. }
  358. }
  359. }
  360. // finish this entrys data if we had changes
  361. if ($is_changed) {
  362. $this->finishEntry();
  363. }
  364. } else {
  365. // LDIF-content mode
  366. // fetch attributes for further processing
  367. $entry_attrs = $entry->getValues();
  368. // sort and put objectclass-attrs to first position
  369. if ($this->_options['sort']) {
  370. ksort($entry_attrs);
  371. if (array_key_exists('objectclass', $entry_attrs)) {
  372. $oc = $entry_attrs['objectclass'];
  373. unset($entry_attrs['objectclass']);
  374. $entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs);
  375. }
  376. }
  377. // write data
  378. if (!$this->_version_written) {
  379. $this->write_version();
  380. }
  381. $this->writeDN($entry->dn());
  382. foreach ($entry_attrs as $attr_name => $attr_values) {
  383. $this->writeAttribute($attr_name, $attr_values);
  384. }
  385. $this->finishEntry();
  386. }
  387. }
  388. }
  389. }
  390. /**
  391. * Write version to LDIF
  392. *
  393. * If the object's version is defined, this method allows to explicitely write the version before an entry is written.
  394. * If not called explicitely, it gets called automatically when writing the first entry.
  395. *
  396. * @return void
  397. */
  398. public function write_version()
  399. {
  400. $this->_version_written = true;
  401. if (!is_null($this->version())) {
  402. return $this->writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP2_LDIF error: unable to write version');
  403. }
  404. }
  405. /**
  406. * Get or set LDIF version
  407. *
  408. * If called without arguments it returns the version of the LDIF file or NULL if no version has been set.
  409. * If called with an argument it sets the LDIF version to VERSION.
  410. * According to RFC 2849 currently the only legal value for VERSION is 1.
  411. *
  412. * @param int $version (optional) LDIF version to set
  413. *
  414. * @return int
  415. */
  416. public function version($version = null)
  417. {
  418. if ($version !== null) {
  419. if ($version != 1) {
  420. $this->dropError('Net_LDAP2_LDIF error: illegal LDIF version set');
  421. } else {
  422. $this->_options['version'] = $version;
  423. }
  424. }
  425. return $this->_options['version'];
  426. }
  427. /**
  428. * Returns the file handle the Net_LDAP2_LDIF object reads from or writes to.
  429. *
  430. * You can, for example, use this to fetch the content of the LDIF file yourself
  431. *
  432. * @return null|resource
  433. */
  434. public function &handle()
  435. {
  436. if (!is_resource($this->_FH)) {
  437. $this->dropError('Net_LDAP2_LDIF error: invalid file resource');
  438. $null = null;
  439. return $null;
  440. } else {
  441. return $this->_FH;
  442. }
  443. }
  444. /**
  445. * Clean up
  446. *
  447. * This method signals that the LDIF object is no longer needed.
  448. * You can use this to free up some memory and close the file handle.
  449. * The file handle is only closed, if it was opened from Net_LDAP2_LDIF.
  450. *
  451. * @return void
  452. */
  453. public function done()
  454. {
  455. // close FH if we opened it
  456. if ($this->_FH_opened) {
  457. fclose($this->handle());
  458. }
  459. // free variables
  460. foreach (get_object_vars($this) as $name => $value) {
  461. unset($this->$name);
  462. }
  463. }
  464. /**
  465. * Returns last error message if error was found.
  466. *
  467. * Example:
  468. * <code>
  469. * $ldif->someAction();
  470. * if ($ldif->error()) {
  471. * echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines();
  472. * }
  473. * </code>
  474. *
  475. * @param boolean $as_string If set to true, only the message is returned
  476. *
  477. * @return false|Net_LDAP2_Error
  478. */
  479. public function error($as_string = false)
  480. {
  481. if (Net_LDAP2::isError($this->_error['error'])) {
  482. return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error'];
  483. } else {
  484. return false;
  485. }
  486. }
  487. /**
  488. * Returns lines that resulted in error.
  489. *
  490. * Perl returns an array of faulty lines in list context,
  491. * but we always just return an int because of PHPs language.
  492. *
  493. * @return int
  494. */
  495. public function error_lines()
  496. {
  497. return $this->_error['line'];
  498. }
  499. /**
  500. * Returns the current Net::LDAP::Entry object.
  501. *
  502. * @return Net_LDAP2_Entry|false
  503. */
  504. public function current_entry()
  505. {
  506. return $this->parseLines($this->current_lines());
  507. }
  508. /**
  509. * Parse LDIF lines of one entry into an Net_LDAP2_Entry object
  510. *
  511. * @param array $lines LDIF lines for one entry
  512. *
  513. * @return Net_LDAP2_Entry|false Net_LDAP2_Entry object for those lines
  514. * @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
  515. */
  516. public function parseLines($lines)
  517. {
  518. // parse lines into an array of attributes and build the entry
  519. $attributes = array();
  520. $dn = false;
  521. foreach ($lines as $line) {
  522. if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
  523. $attr =& $matches[1] . $matches[2];
  524. $delim =& $matches[3];
  525. $data =& $matches[4];
  526. if ($delim == ':') {
  527. // normal data
  528. $attributes[$attr][] = $data;
  529. } elseif ($delim == '::') {
  530. // base64 data
  531. $attributes[$attr][] = base64_decode($data);
  532. } elseif ($delim == ':<') {
  533. // file inclusion
  534. // TODO: Is this the job of the LDAP-client or the server?
  535. $this->dropError('File inclusions are currently not supported');
  536. //$attributes[$attr][] = ...;
  537. } else {
  538. // since the pattern above, the delimeter cannot be something else.
  539. $this->dropError('Net_LDAP2_LDIF parsing error: invalid syntax at parsing entry line: '.$line);
  540. continue;
  541. }
  542. if (strtolower($attr) == 'dn') {
  543. // DN line detected
  544. $dn = $attributes[$attr][0]; // save possibly decoded DN
  545. unset($attributes[$attr]); // remove wrongly added "dn: " attribute
  546. }
  547. } else {
  548. // line not in "attr: value" format -> ignore
  549. // maybe we should rise an error here, but this should be covered by
  550. // next_lines() already. A problem arises, if users try to feed data of
  551. // several entries to this method - the resulting entry will
  552. // get wrong attributes. However, this is already mentioned in the
  553. // methods documentation above.
  554. }
  555. }
  556. if (false === $dn) {
  557. $this->dropError('Net_LDAP2_LDIF parsing error: unable to detect DN for entry');
  558. return false;
  559. } else {
  560. $newentry = Net_LDAP2_Entry::createFresh($dn, $attributes);
  561. return $newentry;
  562. }
  563. }
  564. /**
  565. * Returns the lines that generated the current Net::LDAP::Entry object.
  566. *
  567. * Note that this returns an empty array if no lines have been read so far.
  568. *
  569. * @return array Array of lines
  570. */
  571. public function current_lines()
  572. {
  573. return $this->_lines_cur;
  574. }
  575. /**
  576. * Returns the lines that will generate the next Net::LDAP::Entry object.
  577. *
  578. * If you set $force to TRUE then you can iterate over the lines that build
  579. * up entries manually. Otherwise, iterating is done using {@link read_entry()}.
  580. * Force will move the file pointer forward, thus returning the next entries lines.
  581. *
  582. * Wrapped lines will be unwrapped. Comments are stripped.
  583. *
  584. * @param boolean $force Set this to true if you want to iterate over the lines manually
  585. *
  586. * @return array
  587. */
  588. public function next_lines($force = false)
  589. {
  590. // if we already have those lines, just return them, otherwise read
  591. if (count($this->_lines_next) == 0 || $force) {
  592. $this->_lines_next = array(); // empty in case something was left (if used $force)
  593. $entry_done = false;
  594. $fh = &$this->handle();
  595. $commentmode = false; // if we are in an comment, for wrapping purposes
  596. $datalines_read = 0; // how many lines with data we have read
  597. while (!$entry_done && !$this->eof()) {
  598. $this->_input_line++;
  599. // Read line. Remove line endings, we want only data;
  600. // this is okay since ending spaces should be encoded
  601. $data = rtrim(fgets($fh));
  602. if ($data === false) {
  603. // error only, if EOF not reached after fgets() call
  604. if (!$this->eof()) {
  605. $this->dropError('Net_LDAP2_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line);
  606. }
  607. break;
  608. } else {
  609. if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) {
  610. // Entry is finished if we have an empty line after we had data
  611. $entry_done = true;
  612. // Look ahead if the next EOF is nearby. Comments and empty
  613. // lines at the file end may cause problems otherwise
  614. $current_pos = ftell($fh);
  615. $data = fgets($fh);
  616. while (!feof($fh)) {
  617. if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) {
  618. // only empty lines or comments, continue to seek
  619. // TODO: Known bug: Wrappings for comments are okay but are treaten as
  620. // error, since we do not honor comment mode here.
  621. // This should be a very theoretically case, however
  622. // i am willing to fix this if really necessary.
  623. $this->_input_line++;
  624. $current_pos = ftell($fh);
  625. $data = fgets($fh);
  626. } else {
  627. // Data found if non emtpy line and not a comment!!
  628. // Rewind to position prior last read and stop lookahead
  629. fseek($fh, $current_pos);
  630. break;
  631. }
  632. }
  633. // now we have either the file pointer at the beginning of
  634. // a new data position or at the end of file causing feof() to return true
  635. } else {
  636. // build lines
  637. if (preg_match('/^version:\s(.+)$/', $data, $match)) {
  638. // version statement, set version
  639. $this->version($match[1]);
  640. } elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
  641. // normal attribute: add line
  642. $commentmode = false;
  643. $this->_lines_next[] = trim($data);
  644. $datalines_read++;
  645. } elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
  646. // wrapped data: unwrap if not in comment mode
  647. // note that the \s above is some more liberal than
  648. // the RFC requests as it also matches tabs etc.
  649. if (!$commentmode) {
  650. if ($datalines_read == 0) {
  651. // first line of entry: wrapped data is illegal
  652. $this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
  653. } else {
  654. $last = array_pop($this->_lines_next);
  655. $last = $last.$matches[1];
  656. $this->_lines_next[] = $last;
  657. $datalines_read++;
  658. }
  659. }
  660. } elseif (preg_match('/^#/', $data)) {
  661. // LDIF comments
  662. $commentmode = true;
  663. } elseif (preg_match('/^\s*$/', $data)) {
  664. // empty line but we had no data for this
  665. // entry, so just ignore this line
  666. $commentmode = false;
  667. } else {
  668. $this->dropError('Net_LDAP2_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line);
  669. continue;
  670. }
  671. }
  672. }
  673. }
  674. }
  675. return $this->_lines_next;
  676. }
  677. /**
  678. * Convert an attribute and value to LDIF string representation
  679. *
  680. * It honors correct encoding of values according to RFC 2849.
  681. * Line wrapping will occur at the configured maximum but only if
  682. * the value is greater than 40 chars.
  683. *
  684. * @param string $attr_name Name of the attribute
  685. * @param string $attr_value Value of the attribute
  686. *
  687. * @access protected
  688. * @return string LDIF string for that attribute and value
  689. */
  690. protected function convertAttribute($attr_name, $attr_value)
  691. {
  692. // Handle empty attribute or process
  693. if (strlen($attr_value) == 0) {
  694. $attr_value = " ";
  695. } else {
  696. $base64 = false;
  697. // ASCII-chars that are NOT safe for the
  698. // start and for being inside the value.
  699. // These are the int values of those chars.
  700. $unsafe_init = array(0, 10, 13, 32, 58, 60);
  701. $unsafe = array(0, 10, 13);
  702. // Test for illegal init char
  703. $init_ord = ord(substr($attr_value, 0, 1));
  704. if ($init_ord > 127 || in_array($init_ord, $unsafe_init)) {
  705. $base64 = true;
  706. }
  707. // Test for illegal content char
  708. for ($i = 0; $i < strlen($attr_value); $i++) {
  709. $char_ord = ord(substr($attr_value, $i, 1));
  710. if ($char_ord > 127 || in_array($char_ord, $unsafe)) {
  711. $base64 = true;
  712. }
  713. }
  714. // Test for ending space
  715. if (substr($attr_value, -1) == ' ') {
  716. $base64 = true;
  717. }
  718. // If converting is needed, do it
  719. // Either we have some special chars or a matching "raw" regex
  720. if ($base64 || ($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) {
  721. $attr_name .= ':';
  722. $attr_value = base64_encode($attr_value);
  723. }
  724. // Lowercase attr names if requested
  725. if ($this->_options['lowercase']) $attr_name = strtolower($attr_name);
  726. // Handle line wrapping
  727. if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) {
  728. $attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true);
  729. }
  730. }
  731. return $attr_name.': '.$attr_value;
  732. }
  733. /**
  734. * Convert an entries DN to LDIF string representation
  735. *
  736. * It honors correct encoding of values according to RFC 2849.
  737. *
  738. * @param string $dn UTF8-Encoded DN
  739. *
  740. * @access protected
  741. * @return string LDIF string for that DN
  742. * @todo I am not sure, if the UTF8 stuff is correctly handled right now
  743. */
  744. protected function convertDN($dn)
  745. {
  746. $base64 = false;
  747. // ASCII-chars that are NOT safe for the
  748. // start and for being inside the dn.
  749. // These are the int values of those chars.
  750. $unsafe_init = array(0, 10, 13, 32, 58, 60);
  751. $unsafe = array(0, 10, 13);
  752. // Test for illegal init char
  753. $init_ord = ord(substr($dn, 0, 1));
  754. if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) {
  755. $base64 = true;
  756. }
  757. // Test for illegal content char
  758. for ($i = 0; $i < strlen($dn); $i++) {
  759. $char = substr($dn, $i, 1);
  760. if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
  761. $base64 = true;
  762. }
  763. }
  764. // Test for ending space
  765. if (substr($dn, -1) == ' ') {
  766. $base64 = true;
  767. }
  768. // if converting is needed, do it
  769. return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn;
  770. }
  771. /**
  772. * Writes an attribute to the filehandle
  773. *
  774. * @param string $attr_name Name of the attribute
  775. * @param string|array $attr_values Single attribute value or array with attribute values
  776. *
  777. * @access protected
  778. * @return void
  779. */
  780. protected function writeAttribute($attr_name, $attr_values)
  781. {
  782. // write out attribute content
  783. if (!is_array($attr_values)) {
  784. $attr_values = array($attr_values);
  785. }
  786. foreach ($attr_values as $attr_val) {
  787. $line = $this->convertAttribute($attr_name, $attr_val).PHP_EOL;
  788. $this->writeLine($line, 'Net_LDAP2_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum);
  789. }
  790. }
  791. /**
  792. * Writes a DN to the filehandle
  793. *
  794. * @param string $dn DN to write
  795. *
  796. * @access protected
  797. * @return void
  798. */
  799. protected function writeDN($dn)
  800. {
  801. // prepare DN
  802. if ($this->_options['encode'] == 'base64') {
  803. $dn = $this->convertDN($dn).PHP_EOL;
  804. } elseif ($this->_options['encode'] == 'canonical') {
  805. $dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
  806. } else {
  807. $dn = $dn.PHP_EOL;
  808. }
  809. $this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry '.$this->_entrynum);
  810. }
  811. /**
  812. * Finishes an LDIF entry
  813. *
  814. * @access protected
  815. * @return void
  816. */
  817. protected function finishEntry()
  818. {
  819. $this->writeLine(PHP_EOL, 'Net_LDAP2_LDIF error: unable to close entry '.$this->_entrynum);
  820. }
  821. /**
  822. * Just write an arbitary line to the filehandle
  823. *
  824. * @param string $line Content to write
  825. * @param string $error If error occurs, drop this message
  826. *
  827. * @access protected
  828. * @return true|false
  829. */
  830. protected function writeLine($line, $error = 'Net_LDAP2_LDIF error: unable to write to filehandle')
  831. {
  832. if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) {
  833. $this->dropError($error);
  834. return false;
  835. } else {
  836. return true;
  837. }
  838. }
  839. /**
  840. * Optionally raises an error and pushes the error on the error cache
  841. *
  842. * @param string $msg Errortext
  843. * @param int $line Line in the LDIF that caused the error
  844. *
  845. * @access protected
  846. * @return void
  847. */
  848. protected function dropError($msg, $line = null)
  849. {
  850. $this->_error['error'] = new Net_LDAP2_Error($msg);
  851. if ($line !== null) $this->_error['line'] = $line;
  852. if ($this->_options['onerror'] == 'die') {
  853. die($msg.PHP_EOL);
  854. } elseif ($this->_options['onerror'] == 'warn') {
  855. echo $msg.PHP_EOL;
  856. }
  857. }
  858. }
  859. ?>