001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.message;
029
030import org.apache.http.HeaderElement;
031import org.apache.http.NameValuePair;
032import org.apache.http.annotation.ThreadingBehavior;
033import org.apache.http.annotation.Contract;
034import org.apache.http.util.Args;
035import org.apache.http.util.CharArrayBuffer;
036
037/**
038 * Basic implementation for formatting header value elements.
039 * Instances of this class are stateless and thread-safe.
040 * Derived classes are expected to maintain these properties.
041 *
042 * @since 4.0
043 */
044@Contract(threading = ThreadingBehavior.IMMUTABLE)
045public class BasicHeaderValueFormatter implements HeaderValueFormatter {
046
047    /**
048     * A default instance of this class, for use as default or fallback.
049     * Note that {@link BasicHeaderValueFormatter} is not a singleton, there
050     * can be many instances of the class itself and of derived classes.
051     * The instance here provides non-customized, default behavior.
052     *
053     * @deprecated (4.3) use {@link #INSTANCE}
054     */
055    @Deprecated
056    public final static BasicHeaderValueFormatter DEFAULT = new BasicHeaderValueFormatter();
057
058    public final static BasicHeaderValueFormatter INSTANCE = new BasicHeaderValueFormatter();
059
060    /**
061     * Special characters that can be used as separators in HTTP parameters.
062     * These special characters MUST be in a quoted string to be used within
063     * a parameter value .
064     */
065    public final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t";
066
067    /**
068     * Unsafe special characters that must be escaped using the backslash
069     * character
070     */
071    public final static String UNSAFE_CHARS = "\"\\";
072
073    public BasicHeaderValueFormatter() {
074        super();
075    }
076
077    /**
078     * Formats an array of header elements.
079     *
080     * @param elems     the header elements to format
081     * @param quote     {@code true} to always format with quoted values,
082     *                  {@code false} to use quotes only when necessary
083     * @param formatter         the formatter to use, or {@code null}
084     *                          for the {@link #INSTANCE default}
085     *
086     * @return  the formatted header elements
087     */
088    public static
089        String formatElements(final HeaderElement[] elems,
090                              final boolean quote,
091                              final HeaderValueFormatter formatter) {
092        return (formatter != null ? formatter : BasicHeaderValueFormatter.INSTANCE)
093                .formatElements(null, elems, quote).toString();
094    }
095
096
097    // non-javadoc, see interface HeaderValueFormatter
098    @Override
099    public CharArrayBuffer formatElements(final CharArrayBuffer charBuffer,
100                                          final HeaderElement[] elems,
101                                          final boolean quote) {
102        Args.notNull(elems, "Header element array");
103        final int len = estimateElementsLen(elems);
104        CharArrayBuffer buffer = charBuffer;
105        if (buffer == null) {
106            buffer = new CharArrayBuffer(len);
107        } else {
108            buffer.ensureCapacity(len);
109        }
110
111        for (int i=0; i<elems.length; i++) {
112            if (i > 0) {
113                buffer.append(", ");
114            }
115            formatHeaderElement(buffer, elems[i], quote);
116        }
117
118        return buffer;
119    }
120
121
122    /**
123     * Estimates the length of formatted header elements.
124     *
125     * @param elems     the header elements to format, or {@code null}
126     *
127     * @return  a length estimate, in number of characters
128     */
129    protected int estimateElementsLen(final HeaderElement[] elems) {
130        if ((elems == null) || (elems.length < 1)) {
131            return 0;
132        }
133
134        int result = (elems.length-1) * 2; // elements separated by ", "
135        for (final HeaderElement elem : elems) {
136            result += estimateHeaderElementLen(elem);
137        }
138
139        return result;
140    }
141
142
143
144    /**
145     * Formats a header element.
146     *
147     * @param elem      the header element to format
148     * @param quote     {@code true} to always format with quoted values,
149     *                  {@code false} to use quotes only when necessary
150     * @param formatter         the formatter to use, or {@code null}
151     *                          for the {@link #INSTANCE default}
152     *
153     * @return  the formatted header element
154     */
155    public static
156        String formatHeaderElement(final HeaderElement elem,
157                                   final boolean quote,
158                                   final HeaderValueFormatter formatter) {
159        return (formatter != null ? formatter : BasicHeaderValueFormatter.INSTANCE)
160                .formatHeaderElement(null, elem, quote).toString();
161    }
162
163
164    // non-javadoc, see interface HeaderValueFormatter
165    @Override
166    public CharArrayBuffer formatHeaderElement(final CharArrayBuffer charBuffer,
167                                               final HeaderElement elem,
168                                               final boolean quote) {
169        Args.notNull(elem, "Header element");
170        final int len = estimateHeaderElementLen(elem);
171        CharArrayBuffer buffer = charBuffer;
172        if (buffer == null) {
173            buffer = new CharArrayBuffer(len);
174        } else {
175            buffer.ensureCapacity(len);
176        }
177
178        buffer.append(elem.getName());
179        final String value = elem.getValue();
180        if (value != null) {
181            buffer.append('=');
182            doFormatValue(buffer, value, quote);
183        }
184
185        final int parcnt = elem.getParameterCount();
186        if (parcnt > 0) {
187            for (int i=0; i<parcnt; i++) {
188                buffer.append("; ");
189                formatNameValuePair(buffer, elem.getParameter(i), quote);
190            }
191        }
192
193        return buffer;
194    }
195
196
197    /**
198     * Estimates the length of a formatted header element.
199     *
200     * @param elem      the header element to format, or {@code null}
201     *
202     * @return  a length estimate, in number of characters
203     */
204    protected int estimateHeaderElementLen(final HeaderElement elem) {
205        if (elem == null) {
206            return 0;
207        }
208
209        int result = elem.getName().length(); // name
210        final String value = elem.getValue();
211        if (value != null) {
212            // assume quotes, but no escaped characters
213            result += 3 + value.length(); // ="value"
214        }
215
216        final int parcnt = elem.getParameterCount();
217        if (parcnt > 0) {
218            for (int i=0; i<parcnt; i++) {
219                result += 2 +                   // ; <param>
220                    estimateNameValuePairLen(elem.getParameter(i));
221            }
222        }
223
224        return result;
225    }
226
227
228
229
230    /**
231     * Formats a set of parameters.
232     *
233     * @param nvps      the parameters to format
234     * @param quote     {@code true} to always format with quoted values,
235     *                  {@code false} to use quotes only when necessary
236     * @param formatter         the formatter to use, or {@code null}
237     *                          for the {@link #INSTANCE default}
238     *
239     * @return  the formatted parameters
240     */
241    public static
242        String formatParameters(final NameValuePair[] nvps,
243                                final boolean quote,
244                                final HeaderValueFormatter formatter) {
245        return (formatter != null ? formatter : BasicHeaderValueFormatter.INSTANCE)
246                .formatParameters(null, nvps, quote).toString();
247    }
248
249
250    // non-javadoc, see interface HeaderValueFormatter
251    @Override
252    public CharArrayBuffer formatParameters(final CharArrayBuffer charBuffer,
253                                            final NameValuePair[] nvps,
254                                            final boolean quote) {
255        Args.notNull(nvps, "Header parameter array");
256        final int len = estimateParametersLen(nvps);
257        CharArrayBuffer buffer = charBuffer;
258        if (buffer == null) {
259            buffer = new CharArrayBuffer(len);
260        } else {
261            buffer.ensureCapacity(len);
262        }
263
264        for (int i = 0; i < nvps.length; i++) {
265            if (i > 0) {
266                buffer.append("; ");
267            }
268            formatNameValuePair(buffer, nvps[i], quote);
269        }
270
271        return buffer;
272    }
273
274
275    /**
276     * Estimates the length of formatted parameters.
277     *
278     * @param nvps      the parameters to format, or {@code null}
279     *
280     * @return  a length estimate, in number of characters
281     */
282    protected int estimateParametersLen(final NameValuePair[] nvps) {
283        if ((nvps == null) || (nvps.length < 1)) {
284            return 0;
285        }
286
287        int result = (nvps.length-1) * 2; // "; " between the parameters
288        for (final NameValuePair nvp : nvps) {
289            result += estimateNameValuePairLen(nvp);
290        }
291
292        return result;
293    }
294
295
296    /**
297     * Formats a name-value pair.
298     *
299     * @param nvp       the name-value pair to format
300     * @param quote     {@code true} to always format with a quoted value,
301     *                  {@code false} to use quotes only when necessary
302     * @param formatter         the formatter to use, or {@code null}
303     *                          for the {@link #INSTANCE default}
304     *
305     * @return  the formatted name-value pair
306     */
307    public static
308        String formatNameValuePair(final NameValuePair nvp,
309                                   final boolean quote,
310                                   final HeaderValueFormatter formatter) {
311        return (formatter != null ? formatter : BasicHeaderValueFormatter.INSTANCE)
312                .formatNameValuePair(null, nvp, quote).toString();
313    }
314
315
316    // non-javadoc, see interface HeaderValueFormatter
317    @Override
318    public CharArrayBuffer formatNameValuePair(final CharArrayBuffer charBuffer,
319                                               final NameValuePair nvp,
320                                               final boolean quote) {
321        Args.notNull(nvp, "Name / value pair");
322        final int len = estimateNameValuePairLen(nvp);
323        CharArrayBuffer buffer = charBuffer;
324        if (buffer == null) {
325            buffer = new CharArrayBuffer(len);
326        } else {
327            buffer.ensureCapacity(len);
328        }
329
330        buffer.append(nvp.getName());
331        final String value = nvp.getValue();
332        if (value != null) {
333            buffer.append('=');
334            doFormatValue(buffer, value, quote);
335        }
336
337        return buffer;
338    }
339
340
341    /**
342     * Estimates the length of a formatted name-value pair.
343     *
344     * @param nvp       the name-value pair to format, or {@code null}
345     *
346     * @return  a length estimate, in number of characters
347     */
348    protected int estimateNameValuePairLen(final NameValuePair nvp) {
349        if (nvp == null) {
350            return 0;
351        }
352
353        int result = nvp.getName().length(); // name
354        final String value = nvp.getValue();
355        if (value != null) {
356            // assume quotes, but no escaped characters
357            result += 3 + value.length(); // ="value"
358        }
359        return result;
360    }
361
362
363    /**
364     * Actually formats the value of a name-value pair.
365     * This does not include a leading = character.
366     * Called from {@link #formatNameValuePair formatNameValuePair}.
367     *
368     * @param buffer    the buffer to append to, never {@code null}
369     * @param value     the value to append, never {@code null}
370     * @param quote     {@code true} to always format with quotes,
371     *                  {@code false} to use quotes only when necessary
372     */
373    protected void doFormatValue(final CharArrayBuffer buffer,
374                                 final String value,
375                                 final boolean quote) {
376
377        boolean quoteFlag = quote;
378        if (!quoteFlag) {
379            for (int i = 0; (i < value.length()) && !quoteFlag; i++) {
380                quoteFlag = isSeparator(value.charAt(i));
381            }
382        }
383
384        if (quoteFlag) {
385            buffer.append('"');
386        }
387        for (int i = 0; i < value.length(); i++) {
388            final char ch = value.charAt(i);
389            if (isUnsafe(ch)) {
390                buffer.append('\\');
391            }
392            buffer.append(ch);
393        }
394        if (quoteFlag) {
395            buffer.append('"');
396        }
397    }
398
399
400    /**
401     * Checks whether a character is a {@link #SEPARATORS separator}.
402     *
403     * @param ch        the character to check
404     *
405     * @return  {@code true} if the character is a separator,
406     *          {@code false} otherwise
407     */
408    protected boolean isSeparator(final char ch) {
409        return SEPARATORS.indexOf(ch) >= 0;
410    }
411
412
413    /**
414     * Checks whether a character is {@link #UNSAFE_CHARS unsafe}.
415     *
416     * @param ch        the character to check
417     *
418     * @return  {@code true} if the character is unsafe,
419     *          {@code false} otherwise
420     */
421    protected boolean isUnsafe(final char ch) {
422        return UNSAFE_CHARS.indexOf(ch) >= 0;
423    }
424
425
426} // class BasicHeaderValueFormatter