1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderValueFormatter.java $
3 * $Revision: 574185 $
4 * $Date: 2007-09-10 02:19:47 -0700 (Mon, 10 Sep 2007) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.message;
33
34import org.apache.http.HeaderElement;
35import org.apache.http.NameValuePair;
36import org.apache.http.util.CharArrayBuffer;
37
38
39/**
40 * Basic implementation for formatting header value elements.
41 * Instances of this class are stateless and thread-safe.
42 * Derived classes are expected to maintain these properties.
43 *
44 * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a>
45 * @author and others
46 *
47 *
48 * <!-- empty lines above to avoid 'svn diff' context problems -->
49 * @version $Revision: 574185 $
50 *
51 * @since 4.0
52 */
53public class BasicHeaderValueFormatter implements HeaderValueFormatter {
54
55    /**
56     * A default instance of this class, for use as default or fallback.
57     * Note that {@link BasicHeaderValueFormatter} is not a singleton, there
58     * can be many instances of the class itself and of derived classes.
59     * The instance here provides non-customized, default behavior.
60     */
61    public final static
62        BasicHeaderValueFormatter DEFAULT = new BasicHeaderValueFormatter();
63
64
65    /**
66     * Special characters that can be used as separators in HTTP parameters.
67     * These special characters MUST be in a quoted string to be used within
68     * a parameter value .
69     */
70    public final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t";
71
72
73    /**
74     * Unsafe special characters that must be escaped using the backslash
75     * character
76     */
77    public final static String UNSAFE_CHARS = "\"\\";
78
79
80
81    // public default constructor
82
83
84
85    /**
86     * Formats an array of header elements.
87     *
88     * @param elems     the header elements to format
89     * @param quote     <code>true</code> to always format with quoted values,
90     *                  <code>false</code> to use quotes only when necessary
91     * @param formatter         the formatter to use, or <code>null</code>
92     *                          for the {@link #DEFAULT default}
93     *
94     * @return  the formatted header elements
95     */
96    public final static
97        String formatElements(final HeaderElement[] elems,
98                              final boolean quote,
99                              HeaderValueFormatter formatter) {
100        if (formatter == null)
101            formatter = BasicHeaderValueFormatter.DEFAULT;
102        return formatter.formatElements(null, elems, quote).toString();
103    }
104
105
106    // non-javadoc, see interface HeaderValueFormatter
107    public CharArrayBuffer formatElements(CharArrayBuffer buffer,
108                                          final HeaderElement[] elems,
109                                          final boolean quote) {
110        if (elems == null) {
111            throw new IllegalArgumentException
112                ("Header element array must not be null.");
113        }
114
115        int len = estimateElementsLen(elems);
116        if (buffer == null) {
117            buffer = new CharArrayBuffer(len);
118        } else {
119            buffer.ensureCapacity(len);
120        }
121
122        for (int i=0; i<elems.length; i++) {
123            if (i > 0) {
124                buffer.append(", ");
125            }
126            formatHeaderElement(buffer, elems[i], quote);
127        }
128
129        return buffer;
130    }
131
132
133    /**
134     * Estimates the length of formatted header elements.
135     *
136     * @param elems     the header elements to format, or <code>null</code>
137     *
138     * @return  a length estimate, in number of characters
139     */
140    protected int estimateElementsLen(final HeaderElement[] elems) {
141        if ((elems == null) || (elems.length < 1))
142            return 0;
143
144        int result = (elems.length-1) * 2; // elements separated by ", "
145        for (int i=0; i<elems.length; i++) {
146            result += estimateHeaderElementLen(elems[i]);
147        }
148
149        return result;
150    }
151
152
153
154    /**
155     * Formats a header element.
156     *
157     * @param elem      the header element to format
158     * @param quote     <code>true</code> to always format with quoted values,
159     *                  <code>false</code> to use quotes only when necessary
160     * @param formatter         the formatter to use, or <code>null</code>
161     *                          for the {@link #DEFAULT default}
162     *
163     * @return  the formatted header element
164     */
165    public final static
166        String formatHeaderElement(final HeaderElement elem,
167                                   boolean quote,
168                                   HeaderValueFormatter formatter) {
169        if (formatter == null)
170            formatter = BasicHeaderValueFormatter.DEFAULT;
171        return formatter.formatHeaderElement(null, elem, quote).toString();
172    }
173
174
175    // non-javadoc, see interface HeaderValueFormatter
176    public CharArrayBuffer formatHeaderElement(CharArrayBuffer buffer,
177                                               final HeaderElement elem,
178                                               final boolean quote) {
179        if (elem == null) {
180            throw new IllegalArgumentException
181                ("Header element must not be null.");
182        }
183
184        int len = estimateHeaderElementLen(elem);
185        if (buffer == null) {
186            buffer = new CharArrayBuffer(len);
187        } else {
188            buffer.ensureCapacity(len);
189        }
190
191        buffer.append(elem.getName());
192        final String value = elem.getValue();
193        if (value != null) {
194            buffer.append('=');
195            doFormatValue(buffer, value, quote);
196        }
197
198        final int parcnt = elem.getParameterCount();
199        if (parcnt > 0) {
200            for (int i=0; i<parcnt; i++) {
201                buffer.append("; ");
202                formatNameValuePair(buffer, elem.getParameter(i), quote);
203            }
204        }
205
206        return buffer;
207    }
208
209
210    /**
211     * Estimates the length of a formatted header element.
212     *
213     * @param elem      the header element to format, or <code>null</code>
214     *
215     * @return  a length estimate, in number of characters
216     */
217    protected int estimateHeaderElementLen(final HeaderElement elem) {
218        if (elem == null)
219            return 0;
220
221        int result = elem.getName().length(); // name
222        final String value = elem.getValue();
223        if (value != null) {
224            // assume quotes, but no escaped characters
225            result += 3 + value.length(); // ="value"
226        }
227
228        final int parcnt = elem.getParameterCount();
229        if (parcnt > 0) {
230            for (int i=0; i<parcnt; i++) {
231                result += 2 +                   // ; <param>
232                    estimateNameValuePairLen(elem.getParameter(i));
233            }
234        }
235
236        return result;
237    }
238
239
240
241
242    /**
243     * Formats a set of parameters.
244     *
245     * @param nvps      the parameters to format
246     * @param quote     <code>true</code> to always format with quoted values,
247     *                  <code>false</code> to use quotes only when necessary
248     * @param formatter         the formatter to use, or <code>null</code>
249     *                          for the {@link #DEFAULT default}
250     *
251     * @return  the formatted parameters
252     */
253    public final static
254        String formatParameters(final NameValuePair[] nvps,
255                                final boolean quote,
256                                HeaderValueFormatter formatter) {
257        if (formatter == null)
258            formatter = BasicHeaderValueFormatter.DEFAULT;
259        return formatter.formatParameters(null, nvps, quote).toString();
260    }
261
262
263    // non-javadoc, see interface HeaderValueFormatter
264    public CharArrayBuffer formatParameters(CharArrayBuffer buffer,
265                                            NameValuePair[] nvps,
266                                            boolean quote) {
267        if (nvps == null) {
268            throw new IllegalArgumentException
269                ("Parameters must not be null.");
270        }
271
272        int len = estimateParametersLen(nvps);
273        if (buffer == null) {
274            buffer = new CharArrayBuffer(len);
275        } else {
276            buffer.ensureCapacity(len);
277        }
278
279        for (int i = 0; i < nvps.length; i++) {
280            if (i > 0) {
281                buffer.append("; ");
282            }
283            formatNameValuePair(buffer, nvps[i], quote);
284        }
285
286        return buffer;
287    }
288
289
290    /**
291     * Estimates the length of formatted parameters.
292     *
293     * @param nvps      the parameters to format, or <code>null</code>
294     *
295     * @return  a length estimate, in number of characters
296     */
297    protected int estimateParametersLen(final NameValuePair[] nvps) {
298        if ((nvps == null) || (nvps.length < 1))
299            return 0;
300
301        int result = (nvps.length-1) * 2; // "; " between the parameters
302        for (int i=0; i<nvps.length; i++) {
303            result += estimateNameValuePairLen(nvps[i]);
304        }
305
306        return result;
307    }
308
309
310    /**
311     * Formats a name-value pair.
312     *
313     * @param nvp       the name-value pair to format
314     * @param quote     <code>true</code> to always format with a quoted value,
315     *                  <code>false</code> to use quotes only when necessary
316     * @param formatter         the formatter to use, or <code>null</code>
317     *                          for the {@link #DEFAULT default}
318     *
319     * @return  the formatted name-value pair
320     */
321    public final static
322        String formatNameValuePair(final NameValuePair nvp,
323                                   final boolean quote,
324                                   HeaderValueFormatter formatter) {
325        if (formatter == null)
326            formatter = BasicHeaderValueFormatter.DEFAULT;
327        return formatter.formatNameValuePair(null, nvp, quote).toString();
328    }
329
330
331    // non-javadoc, see interface HeaderValueFormatter
332    public CharArrayBuffer formatNameValuePair(CharArrayBuffer buffer,
333                                               final NameValuePair nvp,
334                                               final boolean quote) {
335        if (nvp == null) {
336            throw new IllegalArgumentException
337                ("NameValuePair must not be null.");
338        }
339
340        int len = estimateNameValuePairLen(nvp);
341        if (buffer == null) {
342            buffer = new CharArrayBuffer(len);
343        } else {
344            buffer.ensureCapacity(len);
345        }
346
347        buffer.append(nvp.getName());
348        final String value = nvp.getValue();
349        if (value != null) {
350            buffer.append('=');
351            doFormatValue(buffer, value, quote);
352        }
353
354        return buffer;
355    }
356
357
358    /**
359     * Estimates the length of a formatted name-value pair.
360     *
361     * @param nvp       the name-value pair to format, or <code>null</code>
362     *
363     * @return  a length estimate, in number of characters
364     */
365    protected int estimateNameValuePairLen(final NameValuePair nvp) {
366        if (nvp == null)
367            return 0;
368
369        int result = nvp.getName().length(); // name
370        final String value = nvp.getValue();
371        if (value != null) {
372            // assume quotes, but no escaped characters
373            result += 3 + value.length(); // ="value"
374        }
375        return result;
376    }
377
378
379    /**
380     * Actually formats the value of a name-value pair.
381     * This does not include a leading = character.
382     * Called from {@link #formatNameValuePair formatNameValuePair}.
383     *
384     * @param buffer    the buffer to append to, never <code>null</code>
385     * @param value     the value to append, never <code>null</code>
386     * @param quote     <code>true</code> to always format with quotes,
387     *                  <code>false</code> to use quotes only when necessary
388     */
389    protected void doFormatValue(final CharArrayBuffer buffer,
390                                 final String value,
391                                 boolean quote) {
392
393        if (!quote) {
394            for (int i = 0; (i < value.length()) && !quote; i++) {
395                quote = isSeparator(value.charAt(i));
396            }
397        }
398
399        if (quote) {
400            buffer.append('"');
401        }
402        for (int i = 0; i < value.length(); i++) {
403            char ch = value.charAt(i);
404            if (isUnsafe(ch)) {
405                buffer.append('\\');
406            }
407            buffer.append(ch);
408        }
409        if (quote) {
410            buffer.append('"');
411        }
412    }
413
414
415    /**
416     * Checks whether a character is a {@link #SEPARATORS separator}.
417     *
418     * @param ch        the character to check
419     *
420     * @return  <code>true</code> if the character is a separator,
421     *          <code>false</code> otherwise
422     */
423    protected boolean isSeparator(char ch) {
424        return SEPARATORS.indexOf(ch) >= 0;
425    }
426
427
428    /**
429     * Checks whether a character is {@link #UNSAFE_CHARS unsafe}.
430     *
431     * @param ch        the character to check
432     *
433     * @return  <code>true</code> if the character is unsafe,
434     *          <code>false</code> otherwise
435     */
436    protected boolean isUnsafe(char ch) {
437        return UNSAFE_CHARS.indexOf(ch) >= 0;
438    }
439
440
441} // class BasicHeaderValueFormatter
442