001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.model.filter; 021 022 023import java.text.Format; 024import java.text.MessageFormat; 025import java.util.Locale; 026 027import org.apache.directory.api.i18n.I18n; 028 029 030/** 031 * An encoder for LDAP filters. 032 * 033 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 034 */ 035public final class FilterEncoder 036{ 037 private static final String[] EMPTY = new String[0]; 038 039 040 private FilterEncoder() 041 { 042 } 043 044 045 /** 046 * Formats a filter and handles encoding of special characters in the value arguments using the 047 * <valueencoding> rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. 048 * <p> 049 * Example of filter template format: <code>(&,(cn={0})(uid={1}))</code> 050 * 051 * @param filterTemplate the filter with placeholders 052 * @param values the values to encode and substitute 053 * @return the formatted filter with escaped values 054 * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template 055 */ 056 public static String format( String filterTemplate, String... values ) 057 { 058 if ( values == null ) 059 { 060 values = EMPTY; 061 } 062 063 MessageFormat mf = new MessageFormat( filterTemplate, Locale.ROOT ); 064 065 // check element count and argument count 066 Format[] formats = mf.getFormatsByArgumentIndex(); 067 if ( formats.length != values.length ) 068 { 069 String msg = "Filter template {0} has {1} placeholders but {2} arguments provided."; 070 throw new IllegalArgumentException( I18n.format( msg, filterTemplate, formats.length, values.length ) ); 071 } 072 073 // encode arguments 074 for ( int i = 0; i < values.length; i++ ) 075 { 076 values[i] = encodeFilterValue( values[i] ); 077 } 078 079 // format the filter 080 return mf.format( values ); 081 } 082 083 084 /** 085 * Handles encoding of special characters in LDAP search filter assertion values using the 086 * <valueencoding> rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. 087 * 088 * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter. 089 * @return Escaped version of <code>value</code> 090 */ 091 public static String encodeFilterValue( String value ) 092 { 093 StringBuilder sb = new StringBuilder( value.length() ); 094 boolean escaped = false; 095 boolean hexPair = false; 096 char hex = '\0'; 097 098 for ( int i = 0; i < value.length(); i++ ) 099 { 100 char ch = value.charAt( i ); 101 102 switch ( ch ) 103 { 104 case '*': 105 if ( escaped ) 106 { 107 sb.append( "\\5C" ); 108 109 if ( hexPair ) 110 { 111 sb.append( hex ); 112 hexPair = false; 113 } 114 115 escaped = false; 116 } 117 118 sb.append( "\\2A" ); 119 break; 120 121 case '(': 122 if ( escaped ) 123 { 124 sb.append( "\\5C" ); 125 126 if ( hexPair ) 127 { 128 sb.append( hex ); 129 hexPair = false; 130 } 131 132 escaped = false; 133 } 134 135 sb.append( "\\28" ); 136 break; 137 138 case ')': 139 if ( escaped ) 140 { 141 sb.append( "\\5C" ); 142 143 if ( hexPair ) 144 { 145 sb.append( hex ); 146 hexPair = false; 147 } 148 149 escaped = false; 150 } 151 152 sb.append( "\\29" ); 153 break; 154 155 case '\0': 156 if ( escaped ) 157 { 158 sb.append( "\\5C" ); 159 160 if ( hexPair ) 161 { 162 sb.append( hex ); 163 hexPair = false; 164 } 165 166 escaped = false; 167 } 168 169 sb.append( "\\00" ); 170 break; 171 172 case '\\': 173 if ( escaped ) 174 { 175 sb.append( "\\5C" ); 176 escaped = false; 177 } 178 else 179 { 180 escaped = true; 181 hexPair = false; 182 } 183 184 break; 185 186 case '0': 187 case '1': 188 case '2': 189 case '3': 190 case '4': 191 case '5': 192 case '6': 193 case '7': 194 case '8': 195 case '9': 196 case 'a': 197 case 'b': 198 case 'c': 199 case 'd': 200 case 'e': 201 case 'f': 202 case 'A': 203 case 'B': 204 case 'C': 205 case 'D': 206 case 'E': 207 case 'F': 208 if ( escaped ) 209 { 210 if ( hexPair ) 211 { 212 sb.append( '\\' ).append( hex ).append( ch ); 213 escaped = false; 214 hexPair = false; 215 } 216 else 217 { 218 hexPair = true; 219 hex = ch; 220 } 221 } 222 else 223 { 224 sb.append( ch ); 225 } 226 227 break; 228 229 default: 230 if ( escaped ) 231 { 232 sb.append( "\\5C" ); 233 234 if ( hexPair ) 235 { 236 sb.append( hex ); 237 hexPair = false; 238 } 239 240 escaped = false; 241 } 242 243 sb.append( ch ); 244 } 245 } 246 247 if ( escaped ) 248 { 249 sb.append( "\\5C" ); 250 } 251 252 return sb.toString(); 253 } 254}