Author: seelmann Date: Sun Nov 4 11:52:59 2012 New Revision: 1405534 URL: http://svn.apache.org/viewvc?rev=1405534&view=rev Log: Fix for DIRSHARED-143 (Provide helper method to escape characters to be used in LDAP Filter literal) Added: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/FilterEncoder.java directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/filter/FilterEncoderTest.java Modified: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/AbstractExprNode.java Modified: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/AbstractExprNode.java URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/AbstractExprNode.java?rev=1405534&r1=1405533&r2=1405534&view=diff ============================================================================== --- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/AbstractExprNode.java (original) +++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/AbstractExprNode.java Sun Nov 4 11:52:59 2012 @@ -205,51 +205,15 @@ public abstract class AbstractExprNode i } val = ( ( StringValue ) value ).getString(); - - for ( int i = 0; i < val.length(); i++ ) + String encodedVal = FilterEncoder.encodeFilterValue( val ); + if ( val.equals( encodedVal ) ) { - char ch = val.charAt( i ); - String replace = null; - - switch ( ch ) - { - case '*': - replace = "\\2A"; - break; - - case '(': - replace = "\\28"; - break; - - case ')': - replace = "\\29"; - break; - - case '\\': - replace = "\\5C"; - break; - - case '\0': - replace = "\\00"; - break; - } - - if ( replace != null ) - { - if ( sb == null ) - { - sb = new StringBuilder( val.length() * 2 ); - sb.append( val.substring( 0, i ) ); - } - sb.append( replace ); - } - else if ( sb != null ) - { - sb.append( ch ); - } + return value; + } + else + { + return new StringValue( encodedVal ); } - - return ( sb == null ? value : new StringValue( sb.toString() ) ); } Added: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/FilterEncoder.java URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/FilterEncoder.java?rev=1405534&view=auto ============================================================================== --- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/FilterEncoder.java (added) +++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/filter/FilterEncoder.java Sun Nov 4 11:52:59 2012 @@ -0,0 +1,133 @@ +/* + * 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 + * + * http://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.shared.ldap.model.filter; + + +import java.text.Format; +import java.text.MessageFormat; + + +/** + * An encoder for LDAP filters. + * + * @author Apache Directory Project + */ +public class FilterEncoder +{ + private static final String[] EMPTY = new String[0]; + + + /** + * Formats a filter and handles encoding of special characters in the value arguments using the + * <valueencoding> rule as described in RFC 4515. + * + * @param filterTemplate the filter with placeholders + * @param values the values to encode and substitute + * @return the formatted filter with escaped values + * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template + */ + public static String format( String filterTemplate, String[] values ) throws IllegalArgumentException + { + if ( values == null ) + { + values = EMPTY; + } + + MessageFormat mf = new MessageFormat( filterTemplate ); + + // check element count and argument count + Format[] formats = mf.getFormatsByArgumentIndex(); + if ( formats.length != values.length ) + { + // TODO: I18n + String msg = "Filter template {0} has {1} placeholders but {2} arguments provided."; + throw new IllegalArgumentException( MessageFormat.format( msg, filterTemplate, formats.length, + values.length ) ); + } + + // encode arguments + for ( int i = 0; i < values.length; i++ ) + { + values[i] = encodeFilterValue( values[i] ); + } + + // format the filter + String format = mf.format( values ); + return format; + } + + + /** + * Handles encoding of special characters in LDAP search filter assertion values using the + * <valueencoding> rule as described in RFC 4515. + * + * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter. + * @return Escaped version of value + */ + public static String encodeFilterValue( String value ) + { + StringBuilder sb = null; + + for ( int i = 0; i < value.length(); i++ ) + { + char ch = value.charAt( i ); + String replace = null; + + switch ( ch ) + { + case '*': + replace = "\\2A"; + break; + + case '(': + replace = "\\28"; + break; + + case ')': + replace = "\\29"; + break; + + case '\\': + replace = "\\5C"; + break; + + case '\0': + replace = "\\00"; + break; + } + + if ( replace != null ) + { + if ( sb == null ) + { + sb = new StringBuilder( value.length() * 2 ); + sb.append( value.substring( 0, i ) ); + } + sb.append( replace ); + } + else if ( sb != null ) + { + sb.append( ch ); + } + } + + return ( sb == null ? value : sb.toString() ); + } +} Added: directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/filter/FilterEncoderTest.java URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/filter/FilterEncoderTest.java?rev=1405534&view=auto ============================================================================== --- directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/filter/FilterEncoderTest.java (added) +++ directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/filter/FilterEncoderTest.java Sun Nov 4 11:52:59 2012 @@ -0,0 +1,122 @@ +/* + * 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 + * + * http://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.shared.ldap.model.filter; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.apache.directory.shared.ldap.model.filter.FilterEncoder; +import org.junit.Test; + + +/** + * Tests for {@link FilterEncoder}. + * + * @author Apache Directory Project + */ +public class FilterEncoderTest +{ + + private static final String[] ZERO = new String[0]; + private static final String[] ONE = new String[] + { "foo" }; + private static final String[] TWO = new String[] + { "foo", "bar" }; + private static final String[] SPECIAL_CHARS = new String[] + { "(\\*\0)" }; + + + @Test + public void testFormatWithNoPlaceholdersAndCorrectArgumentCount() + { + assertEquals( "(cn=foo)", FilterEncoder.format( "(cn=foo)", null ) ); + assertEquals( "(cn=foo)", FilterEncoder.format( "(cn=foo)", ZERO ) ); + } + + + @Test(expected = IllegalArgumentException.class) + public void testFormatWithNoPlaceholdersAndTooManyArguments() + { + FilterEncoder.format( "(cn=foo)", ONE ); + } + + + @Test(expected = IllegalArgumentException.class) + public void testFormatWithPlaceholdersAndTooFewArguments() + { + FilterEncoder.format( "(cn={0})", ZERO ); + } + + + @Test + public void testFormatWithPlaceholdersAndCorrectArgumentCount() + { + assertEquals( "(cn=foo)", FilterEncoder.format( "(cn={0})", ONE ) ); + assertEquals( "(&(cn=foo)(uid=bar))", FilterEncoder.format( "(&(cn={0})(uid={1}))", TWO ) ); + } + + + @Test(expected = IllegalArgumentException.class) + public void testFormatWithPlaceholdersAndTooManyArguments() + { + FilterEncoder.format( "(cn={0})", TWO ); + } + + + @Test + public void testFormatWithPlaceholdersAndSpecialChars() + { + assertEquals( "(cn=\\28\\5C\\2A\\00\\29)", FilterEncoder.format( "(cn={0})", SPECIAL_CHARS ) ); + } + + + @Test + public void testExceptionMessage() + { + try + { + FilterEncoder.format( "(&(cn={0})(uid={1}))", ONE ); + fail( "IllegalArgumentException expected" ); + } + catch ( IllegalArgumentException e ) + { + String message = e.getMessage(); + assertTrue( message.contains( " (&(cn={0})(uid={1})) " ) ); + assertTrue( message.contains( " 2 " ) ); + assertTrue( message.contains( " 1 " ) ); + } + } + + + @Test + public void testEncodeFilterValue() + { + assertEquals( "1234567890", FilterEncoder.encodeFilterValue( "1234567890" ) ); + assertEquals( "\\28", FilterEncoder.encodeFilterValue( "(" ) ); + assertEquals( "\\29", FilterEncoder.encodeFilterValue( ")" ) ); + assertEquals( "\\2A", FilterEncoder.encodeFilterValue( "*" ) ); + assertEquals( "\\5C", FilterEncoder.encodeFilterValue( "\\" ) ); + assertEquals( "\\00", FilterEncoder.encodeFilterValue( "\0" ) ); + assertEquals( "\\28\\2A\\29", FilterEncoder.encodeFilterValue( "(*)" ) ); + } + +}