Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/ApproximateFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class ApproximateFilter implements FilterInterface, Stringable
class ApproximateFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use AttributeValueAssertionTrait;

Expand Down
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/EqualityFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class EqualityFilter implements FilterInterface, Stringable
class EqualityFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use AttributeValueAssertionTrait;

Expand Down
25 changes: 25 additions & 0 deletions src/FreeDSx/Ldap/Search/Filter/FilterAttributeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* This file is part of the FreeDSx LDAP package.
*
* (c) Chad Sikorra <Chad.Sikorra@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FreeDSx\Ldap\Search\Filter;

/**
* A filter that asserts against a single attribute type.
*/
interface FilterAttributeInterface
{
/**
* The attribute type the filter references, or null when it is unspecified (an extensibleMatch without one).
*/
public function getAttribute(): ?string;
}
93 changes: 93 additions & 0 deletions src/FreeDSx/Ldap/Search/Filter/FilterAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

/**
* This file is part of the FreeDSx LDAP package.
*
* (c) Chad Sikorra <Chad.Sikorra@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FreeDSx\Ldap\Search\Filter;

use function array_merge;
use function array_unique;
use function array_values;
use function strtok;
use function strtolower;

/**
* Collects the base attribute names a filter references, so a reader can materialize just those for evaluation.
*/
final class FilterAttributes
{
/**
* Lowercased base attribute names the filter references, or null when the set is indeterminate (an extensibleMatch
* without an attribute, or an unrecognized filter type) and every attribute must therefore be materialized.
*
* @return list<string>|null
*/
public static function referenced(FilterInterface $filter): ?array
{
$names = self::walk($filter);

if ($names === null) {
return null;
}

return array_values(array_unique($names));
}

/**
* @return list<string>|null
*/
private static function walk(FilterInterface $filter): ?array
{
if ($filter instanceof FilterContainerInterface) {
return self::walkChildren($filter->get());
}

if ($filter instanceof NotFilter) {
return self::walk($filter->get());
}

if (!$filter instanceof FilterAttributeInterface) {
return null;
}

$attribute = $filter->getAttribute();

if ($attribute === null) {
return null;
}

return [strtolower((string) strtok($attribute, ';'))];
}

/**
* @param array<FilterInterface> $children
* @return list<string>|null
*/
private static function walkChildren(array $children): ?array
{
$names = [];

foreach ($children as $child) {
$childNames = self::walk($child);

if ($childNames === null) {
return null;
}

$names = array_merge(
$names,
$childNames,
);
}

return $names;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class GreaterThanOrEqualFilter implements FilterInterface, Stringable
class GreaterThanOrEqualFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use AttributeValueAssertionTrait;

Expand Down
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/LessThanOrEqualFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class LessThanOrEqualFilter implements FilterInterface, Stringable
class LessThanOrEqualFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use AttributeValueAssertionTrait;

Expand Down
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/MatchingRuleFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class MatchingRuleFilter implements FilterInterface, Stringable
class MatchingRuleFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
protected const CHOICE_TAG = 9;

Expand Down
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/PresentFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class PresentFilter implements FilterInterface, Stringable
class PresentFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use FilterAttributeTrait;

Expand Down
2 changes: 1 addition & 1 deletion src/FreeDSx/Ldap/Search/Filter/SubstringFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class SubstringFilter implements FilterInterface, Stringable
class SubstringFilter implements FilterInterface, FilterAttributeInterface, Stringable
{
use FilterAttributeTrait;

Expand Down
28 changes: 25 additions & 3 deletions src/FreeDSx/Ldap/Server/Backend/Storage/Adapter/PdoStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ public function list(StorageListOptions $options): EntryStream
$composed->params,
),
$deadline,
$options->attributes,
),
false,
);
Expand All @@ -208,6 +209,7 @@ public function list(StorageListOptions $options): EntryStream
$this->generateRows(
$this->prepareAndExecute($query->sql, $query->params),
$deadline,
$options->attributes,
),
$isPreFiltered,
);
Expand Down Expand Up @@ -397,16 +399,27 @@ private function probeLeafSelectivity(SidecarLeaf $leaf): int
/**
* @return Generator<Entry>
*/
/**
* @param list<string>|null $attributes
*/
private function generateRows(
PooledStatement $stmt,
?float $deadline,
?array $attributes = null,
): Generator {
$allowed = $attributes === null
? null
: array_fill_keys($attributes, true);

while (($row = $stmt->fetch()) !== false) {
if ($deadline !== null && microtime(true) >= $deadline) {
throw new TimeLimitExceededException();
}

$entry = $this->rowToEntry($row);
$entry = $this->rowToEntry(
$row,
$allowed,
);
if ($entry !== null) {
yield $entry;
}
Expand Down Expand Up @@ -515,8 +528,13 @@ private function encodeAttributes(Entry $entry): string
return serialize($attributes);
}

private function rowToEntry(mixed $row): ?Entry
{
/**
* @param array<string, true>|null $allowed Base names to materialize, or null for all.
*/
private function rowToEntry(
mixed $row,
?array $allowed = null,
): ?Entry {
if (!is_array($row)) {
return null;
}
Expand All @@ -541,6 +559,10 @@ private function rowToEntry(mixed $row): ?Entry
$attributes = [];

foreach ($raw as $name => $values) {
if ($allowed !== null && !isset($allowed[strtolower((string) strtok($name, ';'))])) {
continue;
}

$attributes[] = Attribute::fromArray(
$name,
$values,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class StorageListOptions
{
/**
* @param SortKey[] $sortKeys
* @param list<string>|null $attributes Lowercase base attribute names to materialize, or null for all.
* @param (\Closure(string): (bool|null))|null $isIntegerOrderedResolver Resolves whether an attribute orders numerically.
*/
public function __construct(
Expand All @@ -38,6 +39,7 @@ public function __construct(
public readonly int $sizeLimit = 0,
public readonly array $sortKeys = [],
public readonly int $lookthroughLimit = 0,
public readonly ?array $attributes = null,
public readonly ?Closure $isIntegerOrderedResolver = null,
) {}

Expand Down
40 changes: 40 additions & 0 deletions src/FreeDSx/Ldap/Server/Backend/Storage/WritableStorageBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use FreeDSx\Ldap\Control\Control;
use FreeDSx\Ldap\Control\ControlBag;
use FreeDSx\Ldap\Control\Sorting\SortingControl;
use FreeDSx\Ldap\Entry\Attribute;
use FreeDSx\Ldap\Entry\Change;
use FreeDSx\Ldap\Entry\Dn;
use FreeDSx\Ldap\Entry\Entry;
Expand All @@ -34,6 +35,7 @@
use FreeDSx\Ldap\Server\Backend\Write\Command\MoveCommand;
use FreeDSx\Ldap\Server\Backend\Write\Command\UpdateCommand;
use FreeDSx\Ldap\Search\Filter\EqualityFilter;
use FreeDSx\Ldap\Search\Filter\FilterAttributes;
use FreeDSx\Ldap\Server\Backend\Storage\Exception\DnTooLongException;
use FreeDSx\Ldap\Server\Backend\Storage\Exception\InvalidAttributeException;
use FreeDSx\Ldap\Server\Backend\Storage\Exception\StorageIoException;
Expand Down Expand Up @@ -207,6 +209,7 @@ public function search(
? $sortingControl->getSortKeys()
: [],
lookthroughLimit: $limits->maxSearchLookthrough,
attributes: $this->materializedAttributes($request),
isIntegerOrderedResolver: fn(string $attribute): ?bool => $this->schema?->isIntegerOrdered($attribute),
);

Expand Down Expand Up @@ -498,6 +501,43 @@ public function move(
});
}

/**
* Base attribute names storage must materialize (requested plus filter-referenced), or null to materialize all.
*
* @return list<string>|null
*/
private function materializedAttributes(SearchRequest $request): ?array
{
$names = array_map(
static fn(Attribute $attribute): string => strtolower($attribute->getName()),
$request->getAttributes(),
);

if ($names === [] || in_array('*', $names, true) || in_array('+', $names, true)) {
return null;
}

$filterAttributes = FilterAttributes::referenced($request->getFilter());

if ($filterAttributes === null) {
return null;
}

$materialized = [];
foreach ($names as $name) {
if ($name === '1.1') {
continue;
}

$materialized[$name] = true;
}
foreach ($filterAttributes as $attribute) {
$materialized[$attribute] = true;
}

return array_keys($materialized);
}

/**
* Confirm the search base exists.
*
Expand Down
Loading
Loading