You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2026/08/30 22:12:41 UTC
(directory-ldap-api) branch master updated: Added some check of any attribute and matching rules injected in filters
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/directory-ldap-api.git
The following commit(s) were added to refs/heads/master by this push:
new 30dd5db05 Added some check of any attribute and matching rules injected in filters
30dd5db05 is described below
commit 30dd5db05f15d78ce422dda2da28aeab4f5c0805
Author: emmanuel lecharny <el...@apache.org>
AuthorDate: Mon Aug 31 00:12:36 2026 +0200
Added some check of any attribute and matching rules injected in filters
---
.../java/org/apache/directory/api/i18n/I18n.java | 2 +
.../apache/directory/api/i18n/errors.properties | 2 +
.../ldap/client/api/search/AbstractFilter.java | 75 +++++++
.../api/search/AttributeDescriptionFilter.java | 18 +-
.../api/search/AttributeValueAssertionFilter.java | 16 ++
.../api/search/MatchingRuleAssertionFilter.java | 33 ++-
.../ldap/client/api/search/SubstringFilter.java | 15 ++
.../api/search/FilterOperandValidationTest.java | 118 +++++++++++
.../api/ldap/model/entry/AttributeUtils.java | 235 ++++++++++++++++++++-
.../api/ldap/model/filter/FilterParser.java | 2 +-
.../org/apache/directory/api/util/Strings.java | 22 ++
11 files changed, 530 insertions(+), 8 deletions(-)
diff --git a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
index a9cf378f4..4f7e20844 100644
--- a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
+++ b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
@@ -219,6 +219,8 @@ public enum I18n
ERR_04178_CANT_LOAD_KEY_STORE( "ERR_04178_CANT_LOAD_KEY_STORE" ),
ERR_04179_TRUST_STORE_CANT_BE_READ( "ERR_04179_TRUST_STORE_CANT_BE_READ" ),
ERR_04180_FILE_DOES_NOT_EXIST_ON_CLASSPATH( "ERR_04180_FILE_DOES_NOT_EXIST_ON_CLASSPATH" ),
+ ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT( "ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT" ),
+ ERR_04182_INVALID_MATCHING_RULE( "ERR_04182_INVALID_MATCHING_RULE" ),
// template 4200-4300
// None
diff --git a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
index 3fab64105..5a180c5c2 100644
--- a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
+++ b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
@@ -209,6 +209,8 @@ ERR_04177_NO_SUCH_ALGORITHM=LdapClientTrustStoreManager.loadTrustManagers caught
ERR_04178_CANT_LOAD_KEY_STORE=LdapClientTrustStoreManager.loadTrustManagers caught KeyStoreException
ERR_04179_TRUST_STORE_CANT_BE_READ=LdapClientTrustStoreManager.getTrustStore finally block on input stream close operation caught IOException={0}
ERR_04180_FILE_DOES_NOT_EXIST_ON_CLASSPATH=LdapClientTrustStoreManager.getTrustStoreInputStream file does not exist on classpath
+ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT=Invalid attribute description : {0}
+ERR_04182_INVALID_MATCHING_RULE: Invalid Matching Rule : {0}
# api-ldap-client-api template 4200-4300
diff --git a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AbstractFilter.java b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AbstractFilter.java
index 58d56722c..1044d7747 100755
--- a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AbstractFilter.java
+++ b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AbstractFilter.java
@@ -19,6 +19,13 @@
*/
package org.apache.directory.ldap.client.api.search;
+import java.text.ParseException;
+
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.entry.AttributeUtils;
+import org.apache.directory.api.util.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* An abstract class used as a base for all the Filter implementations
@@ -27,6 +34,9 @@ package org.apache.directory.ldap.client.api.search;
*/
/* No qualifier*/abstract class AbstractFilter implements Filter
{
+ /** A logger for this class and its descendant */
+ protected static final Logger LOGGER = LoggerFactory.getLogger( Filter.class );
+
/**
* {@inheritDoc}
*/
@@ -35,4 +45,69 @@ package org.apache.directory.ldap.client.api.search;
{
return build( new StringBuilder() );
}
+
+
+
+ /**
+ * Checks that a matching rule operand is a valid matching rule name
+ * (a keystring) or a numeric OID, as it is concatenated unescaped into
+ * the produced filter. A null or empty matching rule is accepted, as it
+ * simply means 'not set'.
+ *
+ * @param matchingRule The matching rule to check
+ * @return The checked matching rule
+ * @throws IllegalArgumentException If the matching rule is not valid
+ */
+ /* No qualifier*/static String checkMatchingRule( String matchingRule )
+ {
+ if ( ( matchingRule == null ) || matchingRule.isEmpty() )
+ {
+ return matchingRule;
+ }
+
+ if ( isKeyString( matchingRule ) )
+ {
+ return matchingRule;
+ }
+ else
+ {
+ try
+ {
+ AttributeUtils.parseOID( matchingRule, 0 );
+ }
+ catch ( ParseException pe )
+ {
+ throw new IllegalArgumentException( I18n.err( I18n.ERR_04182_INVALID_MATCHING_RULE, matchingRule ) );
+ }
+
+ return matchingRule;
+ }
+ }
+
+
+ /**
+ * Checks a RFC 4512 keystrTing : leadkeychar *keychar
+ *
+ * @param str The string to check
+ * @return true if the string is a valid keystring
+ */
+ private static boolean isKeyString( String str )
+ {
+ if ( str.isEmpty() || !Strings.isAlpha( str.charAt( 0 ) ) )
+ {
+ return false;
+ }
+
+ for ( int i = 1; i < str.length(); i++ )
+ {
+ char c = str.charAt( i );
+
+ if ( !Strings.isAlpha( c ) && !Strings.isDigit( c ) && ( c != '-' ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeDescriptionFilter.java b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeDescriptionFilter.java
index 156a065ae..d352b6159 100755
--- a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeDescriptionFilter.java
+++ b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeDescriptionFilter.java
@@ -19,6 +19,11 @@
*/
package org.apache.directory.ldap.client.api.search;
+import java.text.ParseException;
+
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.entry.AttributeUtils;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
/**
* This class is used to handle the Present filter (ie, attr =* )
@@ -27,7 +32,7 @@ package org.apache.directory.ldap.client.api.search;
*/
/* No qualifier*/final class AttributeDescriptionFilter extends AbstractFilter
{
- /** The attribute that must be prersent */
+ /** The attribute that must be present */
private String attribute;
@@ -38,6 +43,17 @@ package org.apache.directory.ldap.client.api.search;
*/
private AttributeDescriptionFilter( String attribute )
{
+ // Check that the attribute is valid
+ try
+ {
+ AttributeUtils.parseAttribute( attribute, true, FilterParser.STRICT );
+ }
+ catch ( ParseException pe )
+ {
+ LOGGER.error( I18n.err( I18n.ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT ), attribute );
+ throw new IllegalArgumentException();
+ }
+
this.attribute = attribute;
}
diff --git a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeValueAssertionFilter.java b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeValueAssertionFilter.java
index 1fa782cdd..da23ab294 100755
--- a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeValueAssertionFilter.java
+++ b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/AttributeValueAssertionFilter.java
@@ -20,7 +20,12 @@
package org.apache.directory.ldap.client.api.search;
+import java.text.ParseException;
+
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.filter.FilterEncoder;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
/**
@@ -49,6 +54,17 @@ import org.apache.directory.api.ldap.model.filter.FilterEncoder;
*/
private AttributeValueAssertionFilter( String attribute, String value, FilterOperator operator )
{
+ // Check that the attribute is valid
+ try
+ {
+ AttributeUtils.parseAttribute( attribute, true, FilterParser.STRICT );
+ }
+ catch ( ParseException pe )
+ {
+ LOGGER.error( I18n.err( I18n.ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT ), attribute );
+ throw new IllegalArgumentException();
+ }
+
this.attribute = attribute;
this.value = value;
this.operator = operator;
diff --git a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/MatchingRuleAssertionFilter.java b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/MatchingRuleAssertionFilter.java
index b665a0b26..be541376c 100755
--- a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/MatchingRuleAssertionFilter.java
+++ b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/MatchingRuleAssertionFilter.java
@@ -20,7 +20,13 @@
package org.apache.directory.ldap.client.api.search;
+import java.text.ParseException;
+
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.filter.FilterEncoder;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
+import org.apache.directory.api.util.Strings;
/**
@@ -53,9 +59,22 @@ import org.apache.directory.api.ldap.model.filter.FilterEncoder;
* @param value The value to test for
* @param operator The FilterOperator
*/
- MatchingRuleAssertionFilter( String attribute, String value,
- FilterOperator operator )
+ MatchingRuleAssertionFilter( String attribute, String value, FilterOperator operator )
{
+ // Check that the attribute is valid. It may be null
+ if ( !Strings.isEmpty( attribute ) )
+ {
+ try
+ {
+ AttributeUtils.parseAttribute( attribute, true, FilterParser.STRICT );
+ }
+ catch ( ParseException pe )
+ {
+ LOGGER.error( I18n.err( I18n.ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT ), attribute );
+ throw new IllegalArgumentException();
+ }
+ }
+
this.attribute = attribute;
this.value = value;
this.operator = operator;
@@ -97,7 +116,8 @@ import org.apache.directory.api.ldap.model.filter.FilterEncoder;
*/
public MatchingRuleAssertionFilter setMatchingRule( String matchingRule )
{
- this.matchingRule = matchingRule;
+ this.matchingRule = checkMatchingRule( matchingRule );
+
return this;
}
@@ -109,7 +129,8 @@ import org.apache.directory.api.ldap.model.filter.FilterEncoder;
*/
public MatchingRuleAssertionFilter useDnAttributes()
{
- this.useDnAttributes = true;
+ useDnAttributes = true;
+
return this;
}
@@ -121,18 +142,22 @@ import org.apache.directory.api.ldap.model.filter.FilterEncoder;
public StringBuilder build( StringBuilder builder )
{
builder.append( "(" );
+
if ( attribute != null )
{
builder.append( attribute );
}
+
if ( useDnAttributes )
{
builder.append( ":dn" );
}
+
if ( matchingRule != null && !matchingRule.isEmpty() )
{
builder.append( ":" ).append( matchingRule );
}
+
return builder.append( operator.operator() )
.append( FilterEncoder.encodeFilterValue( value ) ).append( ")" );
}
diff --git a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/SubstringFilter.java b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/SubstringFilter.java
index 5eb7d30b1..98f08b8b6 100644
--- a/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/SubstringFilter.java
+++ b/ldap/client/api/src/main/java/org/apache/directory/ldap/client/api/search/SubstringFilter.java
@@ -20,10 +20,14 @@
package org.apache.directory.ldap.client.api.search;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.filter.FilterEncoder;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
import org.apache.directory.api.util.Strings;
@@ -57,6 +61,17 @@ final class SubstringFilter extends AbstractFilter
*/
private SubstringFilter( String attribute, String initial, String[] any, String end )
{
+ // Check that the attribute is valid
+ try
+ {
+ AttributeUtils.parseAttribute( attribute, true, FilterParser.STRICT );
+ }
+ catch ( ParseException pe )
+ {
+ LOGGER.error( I18n.err( I18n.ERR_04181_INVALID_FILTER_ATTRIBUTE_DESCRIPTIONT ), attribute );
+ throw new IllegalArgumentException();
+ }
+
this.attribute = attribute;
this.initial = initial;
diff --git a/ldap/client/api/src/test/java/org/apache/directory/ldap/client/api/search/FilterOperandValidationTest.java b/ldap/client/api/src/test/java/org/apache/directory/ldap/client/api/search/FilterOperandValidationTest.java
new file mode 100644
index 000000000..9a8f56958
--- /dev/null
+++ b/ldap/client/api/src/test/java/org/apache/directory/ldap/client/api/search/FilterOperandValidationTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.directory.ldap.client.api.search;
+
+
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.contains;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.extensible;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.present;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.startsWith;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Tests that no operand of the FilterBuilder API can alter the structure of
+ * the produced filter : the attribute description and matching rule operands
+ * are concatenated unescaped, so they must be validated against the RFC 4512
+ * attributedescription grammar. Otherwise an attacker-controlled 'attribute
+ * name' like "uid=admin)(roomNumber" would inject arbitrary clauses through
+ * the library's own safe-construction API.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class FilterOperandValidationTest
+{
+ @Test
+ public void testEqualRejectsFilterInjectionInAttribute()
+ {
+ assertThrows( IllegalArgumentException.class,
+ () -> equal( "uid=admin)(roomNumber", "1234" ) );
+ assertThrows( IllegalArgumentException.class,
+ () -> equal( "cn=*)(uid=admin", "x" ) );
+ assertThrows( IllegalArgumentException.class,
+ () -> equal( "", "x" ) );
+ assertThrows( IllegalArgumentException.class,
+ () -> equal( null, "x" ) );
+ }
+
+
+ @Test
+ public void testPresentRejectsFilterInjectionInAttribute()
+ {
+ assertThrows( IllegalArgumentException.class,
+ () -> present( "cn=x)(uid=admin" ) );
+ }
+
+
+ @Test
+ public void testSubstringRejectsFilterInjectionInAttribute()
+ {
+ assertThrows( IllegalArgumentException.class,
+ () -> startsWith( "cn=x)(uid=admin", "adm" ) );
+ assertThrows( IllegalArgumentException.class,
+ () -> contains( "cn=x)(uid=admin", "adm" ) );
+ }
+
+
+ @Test
+ public void testExtensibleRejectsFilterInjectionInAttribute()
+ {
+ assertThrows( IllegalArgumentException.class,
+ () -> extensible( "cn:dn:=x)(uid=admin", "x" ) );
+ }
+
+
+ @Test
+ public void testSetMatchingRuleRejectsFilterInjection()
+ {
+ assertThrows( IllegalArgumentException.class,
+ () -> extensible( "cn", "x" ).setMatchingRule( "caseExactMatch:=x)(cn=*" ) );
+ assertThrows( IllegalArgumentException.class,
+ () -> extensible( "cn", "x" ).setMatchingRule( "dn:caseExactMatch" ) );
+ }
+
+
+ @Test
+ public void testValidOperandsStillAccepted()
+ {
+ // keystring attribute
+ assertEquals( "(cn=x)", equal( "cn", "x" ).toString() );
+
+ // Keystring with spacek
+ assertEquals( "(cn =x)", equal( "cn ", "x" ).toString() );
+
+ // numeric OID attribute
+ assertEquals( "(2.5.4.3=x)", equal( "2.5.4.3", "x" ).toString() );
+
+ // attribute description with options
+ assertEquals( "(userCertificate;binary=*)", present( "userCertificate;binary" ).toString() );
+ assertEquals( "(cn;lang-en=x)", equal( "cn;lang-en", "x" ).toString() );
+
+ // matching rule as a name and as an OID
+ assertEquals( "(cn:caseExactMatch:=x)",
+ extensible( "cn", "x" ).setMatchingRule( "caseExactMatch" ).toString() );
+ assertEquals( "(cn:2.5.13.5:=x)",
+ extensible( "cn", "x" ).setMatchingRule( "2.5.13.5" ).toString() );
+ }
+}
diff --git a/ldap/model/src/main/java/org/apache/directory/api/ldap/model/entry/AttributeUtils.java b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/entry/AttributeUtils.java
index 81953b363..5680da355 100644
--- a/ldap/model/src/main/java/org/apache/directory/api/ldap/model/entry/AttributeUtils.java
+++ b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/entry/AttributeUtils.java
@@ -199,6 +199,42 @@ public final class AttributeUtils
}
+ /**
+ * Parse attribute's options :
+ *
+ * <pre>
+ * options = *( ';' option )
+ * option = 1*keychar
+ * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
+ * </pre>
+ *
+ * @param str The parsed option
+ * @param pos The position in the parsed option string
+ * @exception ParseException The parsed option is invalid
+ */
+ private static int parseOptions( String attribute, int pos ) throws ParseException
+ {
+ while ( Strings.isCharASCII( attribute, pos, ';' ) )
+ {
+ pos++;
+
+ // We have an option
+ if ( !Chars.isAlphaDigitMinus( attribute, pos ) )
+ {
+ // We must have at least one keychar
+ throw new ParseException( I18n.err( I18n.ERR_13201_EMPTY_OPTION_NOT_ALLOWED ), pos );
+ }
+
+ pos++;
+
+ while ( Chars.isAlphaDigitMinus( attribute, pos ) )
+ {
+ pos++;
+ }
+ }
+
+ return pos;
+ }
/**
@@ -286,6 +322,57 @@ public final class AttributeUtils
}
+ /**
+ * Parse a number :
+ *
+ * <pre>
+ * number = '0' | '1'..'9' digits
+ * digits = '0'..'9'*
+ * </pre>
+ *
+ * @param filter The number in the filter
+ * @param pos The position in the parsed filter string
+ * @return The new position if a number has been found, -1 otherwise
+ */
+ private static int parseNumber( String filter, int pos )
+ {
+ char c = Strings.charAt( filter, pos );
+
+ switch ( c )
+ {
+ case '0':
+ // If we get a starting '0', we should get out
+ pos++;
+
+ return pos;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ pos++;
+
+ break;
+
+ default:
+ // Not a number.
+ return -1;
+ }
+
+ while ( Chars.isDigit( filter, pos ) )
+ {
+ pos++;
+ }
+
+ return pos;
+ }
+
+
/**
* Parse a number :
*
@@ -381,8 +468,63 @@ public final class AttributeUtils
}
+ /**
+ * Parse an OID.
+ *
+ * numericoid = number 1*( '.' number )
+ * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
+ *
+ * @param str The OID to parse
+ * @param pos The current position in the string
+ * @throws ParseException If we don't have a valid OID
+ */
+ public static int parseOID( String str, int pos ) throws ParseException
+ {
+ // We have an OID
+ pos = parseNumber( str, pos );
+
+ if ( pos == -1 )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13202_INVALID_OID_MISSING_NUMBER ), pos );
+ }
+
+ // We must have at least one '.' number
+ if ( !Strings.isCharASCII( str, pos, '.' ) )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13221_INVALID_OID_MISSING_DOT ), pos );
+ }
+
+ pos++;
+ pos = parseNumber( str, pos );
+
+ if ( pos == -1 )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13202_INVALID_OID_MISSING_NUMBER ), pos );
+ }
+ while ( true )
+ {
+ // Break if we get something which is not a '.'
+ if ( !Strings.isCharASCII( str, pos, '.' ) )
+ {
+ break;
+ }
+
+ pos++;
+
+ pos = parseNumber( str, pos );
+
+ if ( pos == -1 )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13202_INVALID_OID_MISSING_NUMBER ), pos );
+ }
+ }
+
+ return pos;
+ }
+
+
/**
* Parse an OID.
*
@@ -504,8 +646,6 @@ public final class AttributeUtils
}
-
-
/**
* Parse an attribute. The grammar is :
* attributedescription = attributetype options
@@ -577,6 +717,97 @@ public final class AttributeUtils
}
+ /**
+ * Parse an attribute. The grammar is :
+ * attributedescription = attributetype options
+ * attributetype = oid
+ * oid = descr / numericoid
+ * descr = keystring
+ * numericoid = number 1*( '.' number )
+ * options = *( ';' option )
+ * option = 1*keychar
+ * keystring = leadkeychar *keychar
+ * leadkeychar = 'a'-z' | 'A'-'Z'
+ * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
+ * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
+ *
+ * @param bytes The parsed attribute,
+ * @param pos The position of the attribute in the current string
+ * @param withOption A flag set if we want to parse the options
+ * @param relaxed A flag set if we want to parse without being too strict
+ * @return The parsed attribute if valid
+ * @throws ParseException If we had an issue while parsing the attribute
+ */
+ public static String parseAttribute( String attribute, boolean withOption, boolean relaxed )
+ throws ParseException
+ {
+ int pos = 0;
+
+ String trimmedAttribute = Strings.trim( attribute );
+
+ if ( Strings.isEmpty( trimmedAttribute ) )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13222_EMPTY_ATTRIBUTE ), pos );
+ }
+
+ // We must have an OID or an DESCR first
+ char c = attribute.charAt( pos );
+
+ if ( Chars.isAlpha( c ) )
+ {
+ pos++;
+
+ // A DESCR
+ while ( Chars.isAlphaDigitMinus( trimmedAttribute, pos )
+ || ( relaxed && Strings.isCharASCII( trimmedAttribute, pos, '_' ) ) )
+ {
+ pos++;
+ }
+
+ // Parse the options if needed
+ if ( withOption )
+ {
+ pos = parseOptions( trimmedAttribute, pos );
+ }
+
+ // Do we have trailing chars?
+ String result = trimmedAttribute.substring( 0, pos );
+
+ if ( pos < trimmedAttribute.length() )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13223_BAD_CHAR_IN_ATTRIBUTE ), pos );
+ }
+
+ return result;
+ }
+ else if ( Chars.isDigit( c ) )
+ {
+ // Parse the OID
+ pos = parseOID( trimmedAttribute, pos );
+
+ // Parse the options
+ if ( withOption )
+ {
+ pos = parseOptions( trimmedAttribute, pos );
+ }
+
+ // Do we have trailing chars?
+ String result = trimmedAttribute.substring( 0, pos );
+
+ if ( pos < trimmedAttribute.length() )
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13223_BAD_CHAR_IN_ATTRIBUTE ), pos );
+ }
+
+ return result;
+ }
+ else
+ {
+ throw new ParseException( I18n.err( I18n.ERR_13223_BAD_CHAR_IN_ATTRIBUTE ), pos );
+ }
+ }
+
+
/**
* A method to apply a modification to an existing entry.
*
diff --git a/ldap/model/src/main/java/org/apache/directory/api/ldap/model/filter/FilterParser.java b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/filter/FilterParser.java
index 3feca02bc..272bd11fa 100644
--- a/ldap/model/src/main/java/org/apache/directory/api/ldap/model/filter/FilterParser.java
+++ b/ldap/model/src/main/java/org/apache/directory/api/ldap/model/filter/FilterParser.java
@@ -44,7 +44,7 @@ import org.apache.directory.api.util.Unicode;
public final class FilterParser
{
// The default parsing mode is STRICT
- private static final boolean STRICT = false;
+ public static final boolean STRICT = false;
private FilterParser()
{
diff --git a/util/src/main/java/org/apache/directory/api/util/Strings.java b/util/src/main/java/org/apache/directory/api/util/Strings.java
index 4ec77563a..883035215 100644
--- a/util/src/main/java/org/apache/directory/api/util/Strings.java
+++ b/util/src/main/java/org/apache/directory/api/util/Strings.java
@@ -1019,6 +1019,28 @@ public final class Strings
}
+ /**
+ * Tells if a char is alphabetic ('a'-'z' or 'A'-'Z')
+ * @param c The char to check
+ * @return <code>true</code> if the char is alphabetic
+ */
+ public static boolean isAlpha( char c )
+ {
+ return ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) );
+ }
+
+
+ /**
+ * Tells if a char is numeric ('0'-'9')
+ * @param c The char to check
+ * @return <code>true</code> if the char is numberic
+ */
+ public static boolean isDigit( char c )
+ {
+ return ( c >= '0' ) && ( c <= '9' );
+ }
+
+
/**
* <p>
* Removes spaces (char <= 32) from both start and ends of this String,