Author: elecharny Date: Thu Jul 10 04:22:50 2008 New Revision: 675519 URL: http://svn.apache.org/viewvc?rev=675519&view=rev Log: Fix for DIRSERVER-1196 (escaped chars in a filter are not un-escaped) Modified: directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/jndi/SearchIT.java directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/normalization/NormalizingVisitor.java directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/util/StringTools.java Modified: directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/jndi/SearchIT.java URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/jndi/SearchIT.java?rev=675519&r1=675518&r2=675519&view=diff ============================================================================== --- directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/jndi/SearchIT.java (original) +++ directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/jndi/SearchIT.java Thu Jul 10 04:22:50 2008 @@ -1213,6 +1213,56 @@ assertFalse( results.contains( "cn=testGroup4,ou=groups,ou=system" ) ); assertTrue( results.contains( "cn=testGroup5,ou=groups,ou=system" ) ); } + + + @Test + public void testSearchWithEscapedCharsInFilter() throws NamingException + { + // Create an entry with special chars in the description attribute + LdapContext sysRoot = getSystemContext( service ); + // Create entry cn=Sid Vicious, ou=system + Attributes vicious = new AttributesImpl(); + Attribute ocls = new AttributeImpl( "objectClass" ); + ocls.add( "top" ); + ocls.add( "person" ); + vicious.put( ocls ); + vicious.put( "cn", "Sid Vicious" ); + vicious.put( "sn", "Vicious" ); + vicious.put( "description", "(sex pistols)" ); + DirContext ctx = sysRoot.createSubcontext( "cn=Sid Vicious", vicious ); + assertNotNull( ctx ); + + ctx = ( DirContext ) sysRoot.lookup( "cn=Sid Vicious" ); + assertNotNull( ctx ); + + Attributes attributes = ctx.getAttributes( "" ); + + assertEquals( "(sex pistols)", attributes.get( "description" ).get() ); + + // Now, search for the description + SearchControls controls = new SearchControls(); + controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); + controls.setDerefLinkFlag( false ); + controls.setReturningAttributes( new String[] { "*" } ); + sysRoot.addToEnvironment( JndiPropertyConstants.JNDI_LDAP_DAP_DEREF_ALIASES, + AliasDerefMode.NEVER_DEREF_ALIASES.getJndiValue() ); + HashMap map = new HashMap(); + + NamingEnumeration list = sysRoot.search( "", "(description=\\28sex pistols\\29)", controls ); + + while ( list.hasMore() ) + { + SearchResult result = list.next(); + map.put( result.getName(), result.getAttributes() ); + } + + assertEquals( "Expected number of results returned was incorrect!", 1, map.size() ); + + Attributes attrs = map.get( "cn=Sid Vicious,ou=system" ); + + assertNotNull( attrs.get( "objectClass" ) ); + assertNotNull( attrs.get( "cn" ) ); + } /** Modified: directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/normalization/NormalizingVisitor.java URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/normalization/NormalizingVisitor.java?rev=675519&r1=675518&r2=675519&view=diff ============================================================================== --- directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/normalization/NormalizingVisitor.java (original) +++ directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/normalization/NormalizingVisitor.java Thu Jul 10 04:22:50 2008 @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; +import javax.naming.InvalidNameException; import javax.naming.NamingException; import org.apache.directory.server.schema.registries.Registries; @@ -41,6 +42,7 @@ import org.apache.directory.shared.ldap.filter.SubstringNode; import org.apache.directory.shared.ldap.name.NameComponentNormalizer; import org.apache.directory.shared.ldap.schema.AttributeType; +import org.apache.directory.shared.ldap.util.ByteBuffer; import org.apache.directory.shared.ldap.util.StringTools; import org.slf4j.Logger; @@ -77,6 +79,30 @@ /** + * Chars which need to be escaped in a filter + * '\0' | '(' | ')' | '*' | '\' + */ + private static final boolean[] FILTER_CHAR = + { + true, false, false, false, false, false, false, false, // 00 -> 07 NULL + false, false, false, false, false, false, false, false, // 08 -> 0F + false, false, false, false, false, false, false, false, // 10 -> 17 + false, false, false, false, false, false, false, false, // 18 -> 1F + false, false, false, false, false, false, false, false, // 20 -> 27 + true, true, true, false, false, false, false, false, // 28 -> 2F '(', ')', '*' + false, false, false, false, false, false, false, false, // 30 -> 37 + false, false, false, false, false, false, false, false, // 38 -> 3F + false, false, false, false, false, false, false, false, // 40 -> 47 + false, false, false, false, false, false, false, false, // 48 -> 4F + false, false, false, false, false, false, false, false, // 50 -> 57 + false, false, false, false, true, false, false, false, // 58 -> 5F '\' + false, false, false, false, false, false, false, false, // 60 -> 67 + false, false, false, false, false, false, false, false, // 68 -> 6F + false, false, false, false, false, false, false, false, // 70 -> 77 + false, false, false, false, false, false, false, false // 78 -> 7F + }; + + /** * * Creates a new instance of NormalizingVisitor. * @@ -91,6 +117,118 @@ /** + * Check if the given char is a filter escaped char + * <filterEscapedChars> ::= '\0' | '(' | ')' | '*' | '\' + * + * @param c the char we want to test + * @return true if the char is a pair char only + */ + public static boolean isFilterChar( char c ) + { + return ( ( ( c | 0x7F ) == 0x7F ) && FILTER_CHAR[c & 0x7f] ); + } + + /** + * Decodes sequences of escaped hex within an attribute's value into + * a UTF-8 String. The hex is decoded inline and the complete decoded + * String is returned. + * + * @param str the string containing hex escapes + * @return the decoded string + */ + private static final String decodeEscapedHex( String str ) throws InvalidNameException + { + // create buffer and add everything before start of scan + StringBuffer buf = new StringBuffer(); + ByteBuffer bb = new ByteBuffer(); + boolean escaped = false; + + // start scanning until we find an escaped series of bytes + for ( int ii = 0; ii < str.length(); ii++ ) + { + char c = str.charAt( ii ); + + if ( c == '\\' ) + { + // we have the start of a hex escape sequence + if ( StringTools.isHex( str, ii+1 ) && StringTools.isHex ( str, ii+2 ) ) + { + bb.clear(); + int advancedBy = StringTools.collectEscapedHexBytes( bb, str, ii ); + ii+=advancedBy-1; + buf.append( StringTools.utf8ToString( bb.buffer(), bb.position() ) ); + escaped = false; + continue; + } + else if ( !escaped ) + { + // It may be an escaped char ( '\0', '(', ')', '*', '\' ) + escaped = true; + continue; + } + } + + + if ( escaped ) + { + if ( isFilterChar( c ) ) + { + // It is an escaped char ( '\0', '(', ')', '*', '\' ) + // Stores it into the buffer without the '\' + escaped = false; + buf.append( c ); + continue; + } + else + { + throw new InvalidNameException( "The value must contain valid escaped characters." ); + } + } + else + { + buf.append( str.charAt( ii ) ); + } + } + + if ( escaped ) + { + // We should not have a '\' at the end of the string + throw new InvalidNameException( "The value must not ends with a '\\'." ); + } + + return buf.toString(); + } + + + /** + * Un escape the escaped chars in the value + */ + private void unescapeValue( Value value ) + { + if ( !value.isBinary() ) + { + String valStr = (String)value.getNormalizedValue(); + + if ( StringTools.isEmpty( valStr ) ) + { + return; + } + + try + { + String newStr= decodeEscapedHex( valStr ); + ((ClientStringValue)value).set( newStr ); + return; + } + catch ( InvalidNameException ine ) + { + value.set( null ); + return; + } + } + } + + /** * A private method used to normalize a value * * @param attribute The attribute's ID @@ -111,11 +249,15 @@ { normalized = new ClientStringValue( ( String ) ncn.normalizeByName( attribute, StringTools .utf8ToString( ( byte[] ) value.get() ) ) ); + + unescapeValue( normalized ); } else { normalized = new ClientStringValue( ( String ) ncn.normalizeByName( attribute, ( String ) value .get() ) ); + + unescapeValue( normalized ); } } else Modified: directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/util/StringTools.java URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/util/StringTools.java?rev=675519&r1=675518&r2=675519&view=diff ============================================================================== --- directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/util/StringTools.java (original) +++ directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/util/StringTools.java Thu Jul 10 04:22:50 2008 @@ -3446,7 +3446,17 @@ } - private static int collectEscapedHexBytes( ByteBuffer bb, String str, int index ) + /** + * Collects an hex sequence from a string, and returns the value + * as an integer, after having modified the initial value (the escaped + * hex value is transsformed to the byte it represents). + * + * @param bb the buffer which will contain the unescaped byte + * @param str the initial string with ecaped chars + * @param index the position in the string of the escaped data + * @return the byte as an integer + */ + public static int collectEscapedHexBytes( ByteBuffer bb, String str, int index ) { int advanceBy = 0;