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;
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     * &lt;valueencoding&gt; 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>(&amp;(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     * &lt;valueencoding&gt; 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}