View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.api.ldap.model.filter;
21  
22  
23  import java.text.Format;
24  import java.text.MessageFormat;
25  import java.util.Locale;
26  
27  import org.apache.directory.api.i18n.I18n;
28  
29  
30  /**
31   * An encoder for LDAP filters.
32   * 
33   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
34   */
35  public final class FilterEncoder
36  {
37      private static final String[] EMPTY = new String[0];
38  
39  
40      private FilterEncoder()
41      {
42      }
43  
44  
45      /**
46       * Formats a filter and handles encoding of special characters in the value arguments using the
47       * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
48       * <p>
49       * Example of filter template format: <code>(&amp;,(cn={0})(uid={1}))</code>
50       * 
51       * @param filterTemplate the filter with placeholders
52       * @param values the values to encode and substitute
53       * @return the formatted filter with escaped values
54       * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template
55       */
56      public static String format( String filterTemplate, String... values )
57      {
58          if ( values == null )
59          {
60              values = EMPTY;
61          }
62  
63          MessageFormat mf = new MessageFormat( filterTemplate, Locale.ROOT );
64  
65          // check element count and argument count
66          Format[] formats = mf.getFormatsByArgumentIndex();
67          if ( formats.length != values.length )
68          {
69              String msg = "Filter template {0} has {1} placeholders but {2} arguments provided.";
70              throw new IllegalArgumentException( I18n.format( msg, filterTemplate, formats.length, values.length ) );
71          }
72  
73          // encode arguments
74          for ( int i = 0; i < values.length; i++ )
75          {
76              values[i] = encodeFilterValue( values[i] );
77          }
78  
79          // format the filter
80          return mf.format( values );
81      }
82  
83  
84      /**
85       * Handles encoding of special characters in LDAP search filter assertion values using the
86       * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
87       *
88       * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
89       * @return Escaped version of <code>value</code>
90       */
91      public static String encodeFilterValue( String value )
92      {
93          StringBuilder sb = new StringBuilder( value.length() );
94          boolean escaped = false;
95          boolean hexPair = false;
96          char hex = '\0';
97  
98          for ( int i = 0; i < value.length(); i++ )
99          {
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 }