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 = null;
089
090        for ( int i = 0; i < value.length(); i++ )
091        {
092            char ch = value.charAt( i );
093            String replace = null;
094
095            switch ( ch )
096            {
097                case '*':
098                    replace = "\\2A";
099                    break;
100
101                case '(':
102                    replace = "\\28";
103                    break;
104
105                case ')':
106                    replace = "\\29";
107                    break;
108
109                case '\\':
110                    replace = "\\5C";
111                    break;
112
113                case '\0':
114                    replace = "\\00";
115                    break;
116            }
117
118            if ( replace != null )
119            {
120                if ( sb == null )
121                {
122                    sb = new StringBuilder( value.length() * 2 );
123                    sb.append( value.substring( 0, i ) );
124                }
125                sb.append( replace );
126            }
127            else if ( sb != null )
128            {
129                sb.append( ch );
130            }
131        }
132
133        return ( sb == null ? value : sb.toString() );
134    }
135}