View Javadoc

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