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;
025
026
027/**
028 * An encoder for LDAP filters.
029 * 
030 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
031 */
032public final class FilterEncoder
033{
034    private static final String[] EMPTY = new String[0];
035
036
037    private FilterEncoder()
038    {
039    }
040
041
042    /**
043     * Formats a filter and handles encoding of special characters in the value arguments using the
044     * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
045     * <p>
046     * Example of filter template format: <code>(&(cn={0})(uid={1}))</code>
047     * 
048     * @param filterTemplate the filter with placeholders
049     * @param values the values to encode and substitute
050     * @return the formatted filter with escaped values
051     * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template
052     */
053    public static String format( String filterTemplate, String... values ) throws IllegalArgumentException
054    {
055        if ( values == null )
056        {
057            values = EMPTY;
058        }
059
060        MessageFormat mf = new MessageFormat( filterTemplate );
061
062        // check element count and argument count
063        Format[] formats = mf.getFormatsByArgumentIndex();
064        if ( formats.length != values.length )
065        {
066            // TODO: I18n
067            String msg = "Filter template {0} has {1} placeholders but {2} arguments provided.";
068            throw new IllegalArgumentException( MessageFormat.format( msg, filterTemplate, formats.length,
069                values.length ) );
070        }
071
072        // encode arguments
073        for ( int i = 0; i < values.length; i++ )
074        {
075            values[i] = encodeFilterValue( values[i] );
076        }
077
078        // format the filter
079        String format = mf.format( values );
080        return format;
081    }
082
083
084    /**
085     * Handles encoding of special characters in LDAP search filter assertion values using the
086     * &lt;valueencoding&gt; 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}