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  
26  
27  /**
28   * An encoder for LDAP filters.
29   * 
30   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
31   */
32  public class FilterEncoder
33  {
34      private static final String[] EMPTY = new String[0];
35  
36  
37      /**
38       * Formats a filter and handles encoding of special characters in the value arguments using the
39       * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
40       * <p>
41       * Example of filter template format: <code>(&(cn={0})(uid={1}))</code>
42       * 
43       * @param filterTemplate the filter with placeholders
44       * @param values the values to encode and substitute
45       * @return the formatted filter with escaped values
46       * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template
47       */
48      public static String format( String filterTemplate, String... values ) throws IllegalArgumentException
49      {
50          if ( values == null )
51          {
52              values = EMPTY;
53          }
54  
55          MessageFormat mf = new MessageFormat( filterTemplate );
56  
57          // check element count and argument count
58          Format[] formats = mf.getFormatsByArgumentIndex();
59          if ( formats.length != values.length )
60          {
61              // TODO: I18n
62              String msg = "Filter template {0} has {1} placeholders but {2} arguments provided.";
63              throw new IllegalArgumentException( MessageFormat.format( msg, filterTemplate, formats.length,
64                  values.length ) );
65          }
66  
67          // encode arguments
68          for ( int i = 0; i < values.length; i++ )
69          {
70              values[i] = encodeFilterValue( values[i] );
71          }
72  
73          // format the filter
74          String format = mf.format( values );
75          return format;
76      }
77  
78  
79      /**
80       * Handles encoding of special characters in LDAP search filter assertion values using the
81       * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
82       *
83       * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
84       * @return Escaped version of <code>value</code>
85       */
86      public static String encodeFilterValue( String value )
87      {
88          StringBuilder sb = new StringBuilder( value.length() );
89          boolean escaped = false;
90          boolean hexPair = false;
91          char hex = '\0';
92  
93          for ( int i = 0; i < value.length(); i++ )
94          {
95              char ch = value.charAt( i );
96  
97              switch ( ch )
98              {
99                  case '*':
100                     if ( escaped )
101                     {
102                         sb.append( "\\5C" );
103 
104                         if ( hexPair )
105                         {
106                             sb.append( hex );
107                             hexPair = false;
108                         }
109 
110                         escaped = false;
111                     }
112 
113                     sb.append( "\\2A" );
114                     break;
115 
116                 case '(':
117                     if ( escaped )
118                     {
119                         sb.append( "\\5C" );
120 
121                         if ( hexPair )
122                         {
123                             sb.append( hex );
124                             hexPair = false;
125                         }
126 
127                         escaped = false;
128                     }
129 
130                     sb.append( "\\28" );
131                     break;
132 
133                 case ')':
134                     if ( escaped )
135                     {
136                         sb.append( "\\5C" );
137 
138                         if ( hexPair )
139                         {
140                             sb.append( hex );
141                             hexPair = false;
142                         }
143 
144                         escaped = false;
145                     }
146 
147                     sb.append( "\\29" );
148                     break;
149 
150                 case '\0':
151                     if ( escaped )
152                     {
153                         sb.append( "\\5C" );
154 
155                         if ( hexPair )
156                         {
157                             sb.append( hex );
158                             hexPair = false;
159                         }
160 
161                         escaped = false;
162                     }
163 
164                     sb.append( "\\00" );
165                     break;
166 
167                 case '\\':
168                     if ( escaped )
169                     {
170                         sb.append( "\\5C" );
171                         escaped = false;
172                     }
173                     else
174                     {
175                         escaped = true;
176                         hexPair = false;
177                     }
178 
179                     break;
180 
181                 case '0':
182                 case '1':
183                 case '2':
184                 case '3':
185                 case '4':
186                 case '5':
187                 case '6':
188                 case '7':
189                 case '8':
190                 case '9':
191                 case 'a':
192                 case 'b':
193                 case 'c':
194                 case 'd':
195                 case 'e':
196                 case 'f':
197                 case 'A':
198                 case 'B':
199                 case 'C':
200                 case 'D':
201                 case 'E':
202                 case 'F':
203                     if ( escaped )
204                     {
205                         if ( hexPair )
206                         {
207                             sb.append( '\\' ).append( hex ).append( ch );
208                             escaped = false;
209                             hexPair = false;
210                         }
211                         else
212                         {
213                             hexPair = true;
214                             hex = ch;
215                         }
216                     }
217                     else
218                     {
219                         sb.append( ch );
220                     }
221 
222                     break;
223 
224                 default:
225                     if ( escaped )
226                     {
227                         sb.append( "\\5C" );
228 
229                         if ( hexPair )
230                         {
231                             sb.append( hex );
232                             hexPair = false;
233                         }
234 
235                         escaped = false;
236                     }
237 
238                     sb.append( ch );
239             }
240         }
241 
242         if ( escaped )
243         {
244             sb.append( "\\5C" );
245         }
246 
247         return ( sb == null ? value : sb.toString() );
248     }
249 
250 
251     private static void handleEscaped( boolean escaped, boolean hexPair, char hex, StringBuilder sb )
252     {
253         if ( escaped )
254         {
255             sb.append( "\\5C" );
256 
257             if ( hexPair )
258             {
259                 sb.append( hex );
260                 hexPair = false;
261             }
262 
263             escaped = false;
264         }
265     }
266 }