Commit 94bb8e39 authored by Côme Chilliet's avatar Côme Chilliet
Browse files

feat(ldap) Add ldif parsing and ldif schema importing

issue #1
parent dfca8296
<?php
/*
This code is part of FusionDirectory\Ldap (https://www.fusiondirectory.org/)
Copyright (C) 2020 FusionDirectory
SPDX-License-Identifier: GPL-2.0-or-later
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
declare(strict_types = 1);
namespace FusionDirectory\Ldap;
/**
* This class parses LDIF data
*/
class Ldif
{
/**
* @var ?int
*/
protected $version;
/**
* Whether this LDIF represents changes or records
* @var bool
*/
protected $changes;
/**
* @var array<int, array<string,string|array<string>|array<array<string>>>>
*/
protected $entries;
/**
* LDIF constructor
* @param array<int, array<string,string|array<string>|array<array<string>>>> $entries
*/
public function __construct (bool $changes, array $entries = [], ?int $version = NULL)
{
$this->version = $version;
$this->changes = $changes;
$this->entries = $entries;
}
public function isChangesSet (): bool
{
return $this->changes;
}
/**
* @return array<int, array<string,string|array<string>|array<array<string>>>>
*/
public function getEntries (): array
{
return $this->entries;
}
/**
* @param array<int, array<string,string|array<string>|array<array<string>>>> $entries
* @param array<string,string|array<string>|array<array<string>>> $entry
* @throws \FusionDirectory\Ldap\Exception
*/
static protected function parseLine (
int $lineNumber,
string $fileLine,
?string &$line,
array &$entry,
array &$entries,
int &$entryStart,
?int &$version,
bool &$changes
): void
{
if (preg_match('/^ /', $fileLine) === 1) {
if ($line === NULL) {
throw new Exception(sprintf(_('Error line %s, first line of an entry cannot start with a space'), $lineNumber));
}
/* Append to current line */
$line .= substr($fileLine, 1);
} else {
if ($line !== NULL) {
if (preg_match('/^#/', $line) === 1) {
/* Ignore comment */
} elseif ($line === '-') {
/* Changeset split */
$entry['changesets'][] = [];
} else {
/* Line has ended */
if (strpos($line, ':') === FALSE) {
throw new Exception(sprintf(_('Error line %s, expected ":"'), $lineNumber));
}
list ($key, $value) = explode(':', $line, 2);
$value = ltrim($value);
if (preg_match('/^:/', $value) === 1) {
$value = base64_decode(trim(substr($value, 1)), TRUE);
if ($value === FALSE) {
throw new Exception(sprintf(_('Error line %s, invalid base64 data for attribute "%s"'), $lineNumber, $key));
}
}
if (preg_match('/^</', $value) === 1) {
throw new Exception(sprintf(_('Error line %s, references to an external file are not supported'), $lineNumber));
}
if ($value === '') {
throw new Exception(sprintf(_('Error line %s, attribute "%s" has no value'), $lineNumber, $key));
}
if (($key === 'version') && (count($entry) === 0) && (count($entries) === 0) && ($version === NULL)) {
/* Store version number */
$version = intval($value);
} elseif ($key == 'dn') {
if (count($entry) > 0) {
throw new Exception(sprintf(_('Error line %s, an entry bloc can only have one dn'), $lineNumber));
}
$entry['dn'] = $value;
$entryStart = $lineNumber;
} elseif (count($entry) === 0) {
throw new Exception(sprintf(_('Error line %s, an entry bloc should start with the dn'), $lineNumber));
} elseif ($key === 'changetype') {
$changes = TRUE;
$entry['changetype'] = $value;
$entry['changesets'] = [[]];
} else {
if ($changes && ($key !== 'control')) {
if (!isset($entry['changetype'])) {
throw new Exception(sprintf(_('Error line %s, all entry blocs must set changetype, or none of them should'), $lineNumber));
}
$changeset = array_key_last($entry['changesets']);
if (!isset($entry['changesets'][$changeset][$key])) {
$entry['changesets'][$changeset][$key] = [];
}
$entry['changesets'][$changeset][$key][] = $value;
} else {
if (!isset($entry[$key])) {
$entry[$key] = [];
}
$entry[$key][] = $value;
}
}
}
}
/* Start new line */
$line = ltrim($fileLine);
if ($line == '') {
if (count($entry) > 0) {
/* Entry is finished */
$entries[$entryStart] = $entry;
}
/* Start a new entry */
$entry = [];
$entryStart = -1;
$line = NULL;
}
}
}
/**
* @throws \FusionDirectory\Ldap\Exception
*/
static public function parseString (string $data): Ldif
{
/* First we split the string into lines */
$fileLines = preg_split("/\n/", $data);
if ($fileLines === FALSE) {
throw new Exception('Preg split failed');
}
if (end($fileLines) != '') {
$fileLines[] = '';
}
/* Parsing lines */
$line = NULL;
$entry = [];
$entries = [];
$entryStart = -1;
$version = NULL;
$changes = FALSE;
foreach ($fileLines as $lineNumber => $fileLine) {
static::parseLine($lineNumber, $fileLine, $line, $entry, $entries, $entryStart, $version, $changes);
}
return new Ldif($changes, $entries, $version);
}
/**
* @throws \FusionDirectory\Ldap\Exception
* @param resource $fh
*/
static public function parseFromFileHandle ($fh): Ldif
{
$line = NULL;
$entry = [];
$entries = [];
$entryStart = -1;
$version = NULL;
$changes = FALSE;
$lineNumber = 1;
while (($fileLine = fgets($fh)) !== FALSE) {
static::parseLine($lineNumber, rtrim($fileLine, "\n\r"), $line, $entry, $entries, $entryStart, $version, $changes);
$lineNumber++;
}
if (count($entry) !== 0) {
static::parseLine($lineNumber, '', $line, $entry, $entries, $entryStart, $version, $changes);
}
return new Ldif($changes, $entries, $version);
}
}
......@@ -151,6 +151,58 @@ class Schema
if ($fh === FALSE) {
throw new Exception(implode("\n", $errors));
}
if (preg_match('/\.ldif$/i', $path) === 1) {
$ldifData = Ldif::parseFromFileHandle($fh);
if (!feof($fh)) {
throw new Exception('Reading '.$path.' failed');
}
fclose($fh);
if ($ldifData->isChangesSet()) {
throw new Exception($path.' LDIF file contains changes and not records');
}
$ldifEntries = $ldifData->getEntries();
$data = reset($ldifEntries);
if ($data === FALSE) {
throw new Exception($path.' LDIF file does not contain any entry');
} elseif (count($ldifEntries) > 1) {
throw new Exception($path.' LDIF file contains several entries');
}
return new Schema(
$data['cn'][0] ?? $cn,
$data['olcObjectIdentifier'] ?? [],
$data['olcLdapSyntaxes'] ?? [],
$data['olcAttributeTypes'] ?? [],
$data['olcObjectClasses'] ?? [],
$data['olcDitContentRules'] ?? []
);
} else {
$data = static::parseSchemaContent($fh);
if (!feof($fh)) {
throw new Exception('Reading '.$path.' failed');
}
fclose($fh);
foreach ($data as $key => $defs) {
if (!in_array($key, ['objectidentifier','ldapsyntax','attributetype','objectclass','ditcontentrule'], TRUE)) {
throw new Exception('Unknown item type '.$key.' found in schema file');
}
}
return new Schema(
$cn,
$data['objectidentifier'] ?? [],
$data['ldapsyntax'] ?? [],
$data['attributetype'] ?? [],
$data['objectclass'] ?? [],
$data['ditcontentrule'] ?? []
);
}
}
/**
* @return array<string, array<string>>
* @param resource $fh
*/
static protected function parseSchemaContent($fh): array
{
$currentItem = '';
$currentItemName = '';
$data = [];
......@@ -171,22 +223,7 @@ class Schema
if (($currentItem != '') && ($currentItemName != '')) {
$data[$currentItemName][] = $currentItem;
}
if (!feof($fh)) {
throw new Exception('Reading '.$path.' failed');
}
fclose($fh);
foreach ($data as $key => $defs) {
if (!in_array($key, ['objectidentifier','ldapsyntax','attributetype','objectclass','ditcontentrule'], TRUE)) {
throw new Exception('Unknown item type '.$key.' found in schema file');
}
}
return new Schema(
$cn,
$data['objectidentifier'] ?? [],
$data['ldapsyntax'] ?? [],
$data['attributetype'] ?? [],
$data['objectclass'] ?? [],
$data['ditcontentrule'] ?? []
);
return $data;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment