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 * https://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; 028import org.apache.directory.api.util.Strings; 029 030 031/** 032 * An encoder for LDAP filters. 033 * 034 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 035 */ 036public final class FilterEncoder 037{ 038 private FilterEncoder() 039 { 040 } 041 042 043 /** 044 * Formats a filter and handles encoding of special characters in the value arguments using the 045 * <valueencoding> rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. 046 * <p> 047 * Example of filter template format: <code>(&(cn={0})(uid={1}))</code> 048 * 049 * @param filterTemplate the filter with placeholders 050 * @param values the values to encode and substitute 051 * @return the formatted filter with escaped values 052 * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template 053 */ 054 public static String format( String filterTemplate, String... values ) 055 { 056 if ( values == null ) 057 { 058 values = Strings.EMPTY_STRING_ARRAY; 059 } 060 061 MessageFormat mf = new MessageFormat( filterTemplate, Locale.ROOT ); 062 063 // check element count and argument count 064 Format[] formats = mf.getFormatsByArgumentIndex(); 065 066 if ( formats.length != values.length ) 067 { 068 throw new IllegalArgumentException( I18n.err( I18n.ERR_13300_BAD_PLACE_HOLDERS_NUMBER, filterTemplate, formats.length, values.length ) ); 069 } 070 071 // encode arguments 072 for ( int i = 0; i < values.length; i++ ) 073 { 074 values[i] = encodeFilterValue( values[i] ); 075 } 076 077 // format the filter 078 return mf.format( values ); 079 } 080 081 082 /** 083 * Handles encoding of special characters in LDAP search filter assertion values using the 084 * <valueencoding> rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. 085 * 086 * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter. 087 * @return Escaped version of <code>value</code> 088 */ 089 public static String encodeFilterValue( String value ) 090 { 091 StringBuilder sb = new StringBuilder( value.length() ); 092 boolean escaped = false; 093 boolean hexPair = false; 094 char hex = '\0'; 095 096 for ( int i = 0; i < value.length(); i++ ) 097 { 098 char ch = value.charAt( i ); 099 100 switch ( ch ) 101 { 102 case '*': 103 if ( escaped ) 104 { 105 sb.append( "\\5C" ); 106 107 if ( hexPair ) 108 { 109 sb.append( hex ); 110 hexPair = false; 111 } 112 113 escaped = false; 114 } 115 116 sb.append( "\\2A" ); 117 break; 118 119 case '(': 120 if ( escaped ) 121 { 122 sb.append( "\\5C" ); 123 124 if ( hexPair ) 125 { 126 sb.append( hex ); 127 hexPair = false; 128 } 129 130 escaped = false; 131 } 132 133 sb.append( "\\28" ); 134 break; 135 136 case ')': 137 if ( escaped ) 138 { 139 sb.append( "\\5C" ); 140 141 if ( hexPair ) 142 { 143 sb.append( hex ); 144 hexPair = false; 145 } 146 147 escaped = false; 148 } 149 150 sb.append( "\\29" ); 151 break; 152 153 case '\0': 154 if ( escaped ) 155 { 156 sb.append( "\\5C" ); 157 158 if ( hexPair ) 159 { 160 sb.append( hex ); 161 hexPair = false; 162 } 163 164 escaped = false; 165 } 166 167 sb.append( "\\00" ); 168 break; 169 170 case '\\': 171 if ( escaped ) 172 { 173 sb.append( "\\5C" ); 174 escaped = false; 175 } 176 else 177 { 178 escaped = true; 179 hexPair = false; 180 } 181 182 break; 183 184 case '0': 185 case '1': 186 case '2': 187 case '3': 188 case '4': 189 case '5': 190 case '6': 191 case '7': 192 case '8': 193 case '9': 194 case 'a': 195 case 'b': 196 case 'c': 197 case 'd': 198 case 'e': 199 case 'f': 200 case 'A': 201 case 'B': 202 case 'C': 203 case 'D': 204 case 'E': 205 case 'F': 206 if ( escaped ) 207 { 208 if ( hexPair ) 209 { 210 sb.append( '\\' ).append( hex ).append( ch ); 211 escaped = false; 212 hexPair = false; 213 } 214 else 215 { 216 hexPair = true; 217 hex = ch; 218 } 219 } 220 else 221 { 222 sb.append( ch ); 223 } 224 225 break; 226 227 default: 228 if ( escaped ) 229 { 230 sb.append( "\\5C" ); 231 232 if ( hexPair ) 233 { 234 sb.append( hex ); 235 hexPair = false; 236 } 237 238 escaped = false; 239 } 240 241 sb.append( ch ); 242 } 243 } 244 245 if ( escaped ) 246 { 247 sb.append( "\\5C" ); 248 } 249 250 return sb.toString(); 251 } 252}