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