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     * &lt;valueencoding&gt; 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 ) throws IllegalArgumentException
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            // TODO: I18n
070            String msg = "Filter template {0} has {1} placeholders but {2} arguments provided.";
071            throw new IllegalArgumentException( I18n.format( msg, filterTemplate, formats.length, values.length ) );
072        }
073
074        // encode arguments
075        for ( int i = 0; i < values.length; i++ )
076        {
077            values[i] = encodeFilterValue( values[i] );
078        }
079
080        // format the filter
081        String format = mf.format( values );
082        return format;
083    }
084
085
086    /**
087     * Handles encoding of special characters in LDAP search filter assertion values using the
088     * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
089     *
090     * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
091     * @return Escaped version of <code>value</code>
092     */
093    public static String encodeFilterValue( String value )
094    {
095        StringBuilder sb = new StringBuilder( value.length() );
096        boolean escaped = false;
097        boolean hexPair = false;
098        char hex = '\0';
099
100        for ( int i = 0; i < value.length(); i++ )
101        {
102            char ch = value.charAt( i );
103
104            switch ( ch )
105            {
106                case '*':
107                    if ( escaped )
108                    {
109                        sb.append( "\\5C" );
110
111                        if ( hexPair )
112                        {
113                            sb.append( hex );
114                            hexPair = false;
115                        }
116
117                        escaped = false;
118                    }
119
120                    sb.append( "\\2A" );
121                    break;
122
123                case '(':
124                    if ( escaped )
125                    {
126                        sb.append( "\\5C" );
127
128                        if ( hexPair )
129                        {
130                            sb.append( hex );
131                            hexPair = false;
132                        }
133
134                        escaped = false;
135                    }
136
137                    sb.append( "\\28" );
138                    break;
139
140                case ')':
141                    if ( escaped )
142                    {
143                        sb.append( "\\5C" );
144
145                        if ( hexPair )
146                        {
147                            sb.append( hex );
148                            hexPair = false;
149                        }
150
151                        escaped = false;
152                    }
153
154                    sb.append( "\\29" );
155                    break;
156
157                case '\0':
158                    if ( escaped )
159                    {
160                        sb.append( "\\5C" );
161
162                        if ( hexPair )
163                        {
164                            sb.append( hex );
165                            hexPair = false;
166                        }
167
168                        escaped = false;
169                    }
170
171                    sb.append( "\\00" );
172                    break;
173
174                case '\\':
175                    if ( escaped )
176                    {
177                        sb.append( "\\5C" );
178                        escaped = false;
179                    }
180                    else
181                    {
182                        escaped = true;
183                        hexPair = false;
184                    }
185
186                    break;
187
188                case '0':
189                case '1':
190                case '2':
191                case '3':
192                case '4':
193                case '5':
194                case '6':
195                case '7':
196                case '8':
197                case '9':
198                case 'a':
199                case 'b':
200                case 'c':
201                case 'd':
202                case 'e':
203                case 'f':
204                case 'A':
205                case 'B':
206                case 'C':
207                case 'D':
208                case 'E':
209                case 'F':
210                    if ( escaped )
211                    {
212                        if ( hexPair )
213                        {
214                            sb.append( '\\' ).append( hex ).append( ch );
215                            escaped = false;
216                            hexPair = false;
217                        }
218                        else
219                        {
220                            hexPair = true;
221                            hex = ch;
222                        }
223                    }
224                    else
225                    {
226                        sb.append( ch );
227                    }
228
229                    break;
230
231                default:
232                    if ( escaped )
233                    {
234                        sb.append( "\\5C" );
235
236                        if ( hexPair )
237                        {
238                            sb.append( hex );
239                            hexPair = false;
240                        }
241
242                        escaped = false;
243                    }
244
245                    sb.append( ch );
246            }
247        }
248
249        if ( escaped )
250        {
251            sb.append( "\\5C" );
252        }
253
254        return sb.toString();
255    }
256}