/*
 * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/util/ParameterFormatter.java $
 * $Revision: 480424 $
 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient.util;

import org.apache.commons.httpclient.NameValuePair;

/**
 * <p>
 *  This formatter produces a textual representation of attribute/value pairs. It 
 *  comforms to the generic grammar and formatting rules outlined in the 
 *  <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1">Section 2.1</a>
 *  and  
 *  <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>
 *  of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>
 * </p>
 * <h>2.1 Augmented BNF</h>
 * <p>
 *  Many HTTP/1.1 header field values consist of words separated by LWS or special 
 *  characters. These special characters MUST be in a quoted string to be used within 
 *  a parameter value (as defined in section 3.6).
 * <p>
 * <pre>
 * token          = 1*<any CHAR except CTLs or separators>
 * separators     = "(" | ")" | "<" | ">" | "@"
 *                | "," | ";" | ":" | "\" | <">
 *                | "/" | "[" | "]" | "?" | "="
 *                | "{" | "}" | SP | HT
 * </pre>
 * <p>
 *  A string of text is parsed as a single word if it is quoted using double-quote marks.
 * </p>
 * <pre>
 * quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
 * qdtext         = <any TEXT except <">>
 * </pre>
 * <p>
 *  The backslash character ("\") MAY be used as a single-character quoting mechanism only 
 *  within quoted-string and comment constructs.
 * </p>
 * <pre>
 * quoted-pair    = "\" CHAR
 * </pre>
 * <h>3.6 Transfer Codings</h>
 * <p>
 *  Parameters are in the form of attribute/value pairs.
 * </p>
 * <pre>
 * parameter               = attribute "=" value
 * attribute               = token
 * value                   = token | quoted-string
 * </pre>
 * 
 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
 * 
 * @since 3.0
 */
public class ParameterFormatter {
    
    /**
     * Special characters that can be used as separators in HTTP parameters.
     * These special characters MUST be in a quoted string to be used within
     * a parameter value 
     */
    private static final char[] SEPARATORS   = {
            '(', ')', '<', '>', '@', 
            ',', ';', ':', '\\', '"', 
            '/', '[', ']', '?', '=',
            '{', '}', ' ', '\t'
            };
    
    /**
     * Unsafe special characters that must be escaped using the backslash
     * character
     */
    private static final char[] UNSAFE_CHARS = {
            '"', '\\'
            };
    
    /**
     * This flag determines whether all parameter values must be enclosed in 
     * quotation marks, even if they do not contain any special characters
     */
    private boolean alwaysUseQuotes = true;
    
    /** Default ParameterFormatter constructor */
    public ParameterFormatter() {
        super();
    }
    
    private static boolean isOneOf(char[] chars, char ch) {
        for (int i = 0; i < chars.length; i++) {
            if (ch == chars[i]) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean isUnsafeChar(char ch) {
        return isOneOf(UNSAFE_CHARS, ch);
    }
    
    private static boolean isSeparator(char ch) {
        return isOneOf(SEPARATORS, ch);
    }

    /**
     * Determines whether all parameter values must be enclosed in quotation 
     * marks, even if they do not contain any special characters
     * 
     * @return <tt>true</tt> if all parameter values must be enclosed in 
     * quotation marks, <tt>false</tt> otherwise
     */
    public boolean isAlwaysUseQuotes() {
        return alwaysUseQuotes;
    }
    
    /**
     * Defines whether all parameter values must be enclosed in quotation 
     * marks, even if they do not contain any special characters
     * 
     * @param alwaysUseQuotes
     */
    public void setAlwaysUseQuotes(boolean alwaysUseQuotes) {
        this.alwaysUseQuotes = alwaysUseQuotes;
    }
    
    /**
     * Formats the given parameter value using formatting rules defined
     * in RFC 2616 
     * 
     * @param buffer output buffer 
     * @param value the parameter value to be formatted
     * @param alwaysUseQuotes <tt>true</tt> if the parameter value must 
     * be enclosed in quotation marks, even if it does not contain any special 
     * characters<tt>, false</tt> only if the parameter value contains 
     * potentially unsafe special characters
     */
    public static void formatValue(
            final StringBuffer buffer, final String value, boolean alwaysUseQuotes) {
        if (buffer == null) {
            throw new IllegalArgumentException("String buffer may not be null");
        }
        if (value == null) {
            throw new IllegalArgumentException("Value buffer may not be null");
        }
        if (alwaysUseQuotes) {
            buffer.append('"');
            for (int i = 0; i < value.length(); i++) {
                char ch = value.charAt(i);
                if (isUnsafeChar(ch)) {
                    buffer.append('\\');
                }
                buffer.append(ch);
            }
            buffer.append('"');
        } else {
            int offset = buffer.length();
            boolean unsafe = false;
            for (int i = 0; i < value.length(); i++) {
                char ch = value.charAt(i);
                if (isSeparator(ch)) {
                    unsafe = true;
                }
                if (isUnsafeChar(ch)) {
                    buffer.append('\\');
                }
                buffer.append(ch);
            }
            if (unsafe) {
                buffer.insert(offset, '"');
                buffer.append('"');
            }
        }
    }
    
    /**
     * Produces textual representaion of the attribute/value pair using 
     * formatting rules defined in RFC 2616
     *  
     * @param buffer output buffer 
     * @param param the parameter to be formatted
     */
    public void format(final StringBuffer buffer, final NameValuePair param) {
        if (buffer == null) {
            throw new IllegalArgumentException("String buffer may not be null");
        }
        if (param == null) {
            throw new IllegalArgumentException("Parameter may not be null");
        }
        buffer.append(param.getName());
        String value = param.getValue();
        if (value != null) {
            buffer.append("=");
            formatValue(buffer, value, this.alwaysUseQuotes);
        }
    }
    
    /**
     * Produces textual representaion of the attribute/value pair using 
     * formatting rules defined in RFC 2616
     *  
     * @param param the parameter to be formatted
     * 
     * @return RFC 2616 conformant textual representaion of the 
     * attribute/value pair
     */
    public String format(final NameValuePair param) {
        StringBuffer buffer = new StringBuffer();
        format(buffer, param);
        return buffer.toString();
    }

}
