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 class FilterEncoder
033{
034    private static final String[] EMPTY = new String[0];
035
036
037    /**
038     * Formats a filter and handles encoding of special characters in the value arguments using the
039     * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
040     * <p>
041     * Example of filter template format: <code>(&(cn={0})(uid={1}))</code>
042     * 
043     * @param filterTemplate the filter with placeholders
044     * @param values the values to encode and substitute
045     * @return the formatted filter with escaped values
046     * @throws IllegalArgumentException if the number of values does not match the number of placeholders in the template
047     */
048    public static String format( String filterTemplate, String... values ) throws IllegalArgumentException
049    {
050        if ( values == null )
051        {
052            values = EMPTY;
053        }
054
055        MessageFormat mf = new MessageFormat( filterTemplate );
056
057        // check element count and argument count
058        Format[] formats = mf.getFormatsByArgumentIndex();
059        if ( formats.length != values.length )
060        {
061            // TODO: I18n
062            String msg = "Filter template {0} has {1} placeholders but {2} arguments provided.";
063            throw new IllegalArgumentException( MessageFormat.format( msg, filterTemplate, formats.length,
064                values.length ) );
065        }
066
067        // encode arguments
068        for ( int i = 0; i < values.length; i++ )
069        {
070            values[i] = encodeFilterValue( values[i] );
071        }
072
073        // format the filter
074        String format = mf.format( values );
075        return format;
076    }
077
078
079    /**
080     * Handles encoding of special characters in LDAP search filter assertion values using the
081     * &lt;valueencoding&gt; rule as described in <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>.
082     *
083     * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
084     * @return Escaped version of <code>value</code>
085     */
086    public static String encodeFilterValue( String value )
087    {
088        StringBuilder sb = new StringBuilder( value.length() );
089        boolean escaped = false;
090        boolean hexPair = false;
091        char hex = '\0';
092
093        for ( int i = 0; i < value.length(); i++ )
094        {
095            char ch = value.charAt( i );
096
097            switch ( ch )
098            {
099                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}