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
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ private function buildSchemaValidator(Container $container): SchemaValidator
return new SchemaValidator(
$options->getSchema(),
$options->getSchemaValidationMode(),
equalityResolver: $container->get(EqualityComparatorResolver::class),
);
}

Expand Down
73 changes: 57 additions & 16 deletions src/FreeDSx/Ldap/Entry/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
use Stringable;
use Traversable;

use function array_count_values;
use function array_keys;
use function array_search;
use function array_shift;
use function array_values;
use function count;
Expand Down Expand Up @@ -156,22 +156,13 @@ public function removeValues(
array $values,
bool $caseSensitive = true,
): self {
foreach ($values as $value) {
if ($caseSensitive) {
if (($i = array_search($value, $this->values, true)) !== false) {
unset($this->values[$i]);
}

continue;
}

foreach ($this->values as $i => $existing) {
if (strcasecmp($existing, $value) === 0) {
unset($this->values[$i]);
}
}
if ($values === []) {
return $this;
}
$this->values = array_values($this->values);

$this->values = $caseSensitive
? $this->withoutOneOfEach($values)
: $this->withoutAnyCaseOf($values);

return $this;
}
Expand Down Expand Up @@ -379,4 +370,54 @@ private function options(): Options

return $this->options;
}

/**
* Drops one held value for each value listed, leaving any further occurrences in place.
*
* @param string[] $values
* @return list<string>
*/
private function withoutOneOfEach(array $values): array
{
$remaining = array_count_values($values);
$kept = [];

foreach ($this->values as $value) {
if (($remaining[$value] ?? 0) > 0) {
$remaining[$value]--;

continue;
}

$kept[] = $value;
}

return $kept;
}

/**
* Drops every held value matching a listed value, ignoring ASCII case as strcasecmp does.
*
* @param string[] $values
* @return list<string>
*/
private function withoutAnyCaseOf(array $values): array
{
$removed = [];

foreach ($values as $value) {
$removed[strtolower($value)] = true;
}
$kept = [];

foreach ($this->values as $value) {
if (isset($removed[strtolower($value)])) {
continue;
}

$kept[] = $value;
}

return $kept;
}
}
21 changes: 21 additions & 0 deletions src/FreeDSx/Ldap/Schema/Matching/CanonicalIndexKeyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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\Schema\Matching;

/**
* A rule whose index key is canonical: values sharing a key are equal under it.
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
interface CanonicalIndexKeyInterface extends IndexableComparatorInterface {}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Bit string comparator matching the bits within the 'nnnn'B form (RFC 4517 section 4.2.1).
*/
final class BitStringComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final class BitStringComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
public function equals(
string $a,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\StringPrep;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Case-sensitive comparator (caseExactMatch / caseExactSubstringsMatch / caseExactOrderingMatch) using RFC 4518 prep without case folding.
*/
final readonly class CaseExactComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final readonly class CaseExactComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
private PreparedStringComparator $inner;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\StringPrep;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Case-insensitive string comparator (caseIgnoreMatch / caseIgnoreSubstringsMatch / caseIgnoreOrderingMatch).
*/
final readonly class CaseIgnoreComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final readonly class CaseIgnoreComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
private PreparedStringComparator $inner;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Case-insensitive IA5 (ASCII) string comparator (caseIgnoreIA5Match / caseIgnoreIA5SubstringsMatch).
* Behaviorally identical to CaseIgnoreComparator since IA5 is a subset of ASCII.
*/
final readonly class CaseIgnoreIa5Comparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final readonly class CaseIgnoreIa5Comparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
private CaseIgnoreComparator $inner;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Entry\Dn;
use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* DN equality comparator (distinguishedNameMatch): normalizes both sides before comparing.
*/
final class DistinguishedNameComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final class DistinguishedNameComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
public function equals(
string $a,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\StringPrep;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Numeric string comparator treating spaces as insignificant (RFC 4517 section 4.2.22).
*/
final class NumericStringComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final class NumericStringComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
use NormalizedIndexFormsTrait;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\StringPrep;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* String comparator that applies an RFC 4518 preparation profile, then matches byte-exact.
*/
final readonly class PreparedStringComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final readonly class PreparedStringComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
private OctetStringComparator $matcher;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

namespace FreeDSx\Ldap\Schema\Matching\Comparator;

use FreeDSx\Ldap\Schema\Matching\IndexableComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\CanonicalIndexKeyInterface;
use FreeDSx\Ldap\Schema\Matching\MatchingRuleComparatorInterface;
use FreeDSx\Ldap\Schema\Matching\StringPrep;
use FreeDSx\Ldap\Schema\Matching\SubstringAssertion;

/**
* Telephone number comparator (telephoneNumberMatch): strips spaces and hyphens before comparing case-insensitively.
*/
final class TelephoneNumberComparator implements MatchingRuleComparatorInterface, IndexableComparatorInterface
final class TelephoneNumberComparator implements MatchingRuleComparatorInterface, CanonicalIndexKeyInterface
{
use NormalizedIndexFormsTrait;

Expand Down
21 changes: 16 additions & 5 deletions src/FreeDSx/Ldap/Schema/Matching/EqualityComparatorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,31 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
final readonly class EqualityComparatorResolver
final class EqualityComparatorResolver
{
/**
* @var array<string, MatchingRuleComparatorInterface> Memoized per type, since resolving walks the SUP chain.
*/
private array $resolved = [];

public function __construct(
private Schema $schema,
private MatchingRuleComparatorInterface $default = new CaseIgnoreComparator(),
private readonly Schema $schema,
private readonly MatchingRuleComparatorInterface $default = new CaseIgnoreComparator(),
) {}

/**
* Falls back to the default when the type is undefined or has no rule anything implements.
*/
public function for(string $attributeName): MatchingRuleComparatorInterface
{
// Options are not part of the type, so they are dropped before asking the schema about it.
$equalityOid = $this->schema->getEqualityRuleOid(Attribute::normalizeName($attributeName));
$type = Attribute::normalizeName($attributeName);

return $this->resolved[$type] ??= $this->resolve($type);
}

private function resolve(string $type): MatchingRuleComparatorInterface
{
$equalityOid = $this->schema->getEqualityRuleOid($type);
$comparator = $equalityOid !== null
? $this->schema->getComparator($equalityOid)
: null;
Expand Down
Loading
Loading