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.Args;
34 import org.apache.http.util.CharArrayBuffer;
35
36 /**
37 * Basic implementation for formatting header value elements.
38 * Instances of this class are stateless and thread-safe.
39 * Derived classes are expected to maintain these properties.
40 *
41 * @since 4.0
42 */
43 @Immutable
44 public class BasicHeaderValueFormatter implements HeaderValueFormatter {
45
46 /**
47 * A default instance of this class, for use as default or fallback.
48 * Note that {@link BasicHeaderValueFormatter} is not a singleton, there
49 * can be many instances of the class itself and of derived classes.
50 * The instance here provides non-customized, default behavior.
51 *
52 * @deprecated (4.3) use {@link #INSTANCE}
53 */
54 @Deprecated
55 public final static
56 BasicHeaderValueFormatter DEFAULT = new BasicHeaderValueFormatter();
57
58 public final static BasicHeaderValueFormatter INSTANCE = new BasicHeaderValueFormatter();
59
60 /**
61 * Special characters that can be used as separators in HTTP parameters.
62 * These special characters MUST be in a quoted string to be used within
63 * a parameter value .
64 */
65 public final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t";
66
67 /**
68 * Unsafe special characters that must be escaped using the backslash
69 * character
70 */
71 public final static String UNSAFE_CHARS = "\"\\";
72
73 public BasicHeaderValueFormatter() {
74 super();
75 }
76
77 /**
78 * Formats an array of header elements.
79 *
80 * @param elems the header elements to format
81 * @param quote <code>true</code> to always format with quoted values,
82 * <code>false</code> to use quotes only when necessary
83 * @param formatter the formatter to use, or <code>null</code>
84 * for the {@link #INSTANCE default}
85 *
86 * @return the formatted header elements
87 */
88 public final static
89 String formatElements(final HeaderElement[] elems,
90 final boolean quote,
91 HeaderValueFormatter formatter) {
92 if (formatter == null) {
93 formatter = BasicHeaderValueFormatter.INSTANCE;
94 }
95 return formatter.formatElements(null, elems, quote).toString();
96 }
97
98
99 // non-javadoc, see interface HeaderValueFormatter
100 public CharArrayBuffer formatElements(CharArrayBuffer buffer,
101 final HeaderElement[] elems,
102 final boolean quote) {
103 Args.notNull(elems, "Header element array");
104 final int len = estimateElementsLen(elems);
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</code>
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</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 #INSTANCE default}
152 *
153 * @return the formatted header element
154 */
155 public final static
156 String formatHeaderElement(final HeaderElement elem,
157 final boolean quote,
158 HeaderValueFormatter formatter) {
159 if (formatter == null) {
160 formatter = BasicHeaderValueFormatter.INSTANCE;
161 }
162 return formatter.formatHeaderElement(null, elem, quote).toString();
163 }
164
165
166 // non-javadoc, see interface HeaderValueFormatter
167 public CharArrayBuffer formatHeaderElement(CharArrayBuffer buffer,
168 final HeaderElement elem,
169 final boolean quote) {
170 Args.notNull(elem, "Header element");
171 final int len = estimateHeaderElementLen(elem);
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</code>
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</code> to always format with quoted values,
235 * <code>false</code> to use quotes only when necessary
236 * @param formatter the formatter to use, or <code>null</code>
237 * for the {@link #INSTANCE default}
238 *
239 * @return the formatted parameters
240 */
241 public final static
242 String formatParameters(final NameValuePair[] nvps,
243 final boolean quote,
244 HeaderValueFormatter formatter) {
245 if (formatter == null) {
246 formatter = BasicHeaderValueFormatter.INSTANCE;
247 }
248 return formatter.formatParameters(null, nvps, quote).toString();
249 }
250
251
252 // non-javadoc, see interface HeaderValueFormatter
253 public CharArrayBuffer formatParameters(CharArrayBuffer buffer,
254 final NameValuePair[] nvps,
255 final boolean quote) {
256 Args.notNull(nvps, "Header parameter array");
257 final int len = estimateParametersLen(nvps);
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</code>
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</code> to always format with a quoted value,
301 * <code>false</code> to use quotes only when necessary
302 * @param formatter the formatter to use, or <code>null</code>
303 * for the {@link #INSTANCE default}
304 *
305 * @return the formatted name-value pair
306 */
307 public final static
308 String formatNameValuePair(final NameValuePair nvp,
309 final boolean quote,
310 HeaderValueFormatter formatter) {
311 if (formatter == null) {
312 formatter = BasicHeaderValueFormatter.INSTANCE;
313 }
314 return formatter.formatNameValuePair(null, nvp, quote).toString();
315 }
316
317
318 // non-javadoc, see interface HeaderValueFormatter
319 public CharArrayBuffer formatNameValuePair(CharArrayBuffer buffer,
320 final NameValuePair nvp,
321 final boolean quote) {
322 Args.notNull(nvp, "Name / value pair");
323 final int len = estimateNameValuePairLen(nvp);
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</code>
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</code>
369 * @param value the value to append, never <code>null</code>
370 * @param quote <code>true</code> to always format with quotes,
371 * <code>false</code> to use quotes only when necessary
372 */
373 protected void doFormatValue(final CharArrayBuffer buffer,
374 final String value,
375 boolean quote) {
376
377 if (!quote) {
378 for (int i = 0; (i < value.length()) && !quote; i++) {
379 quote = isSeparator(value.charAt(i));
380 }
381 }
382
383 if (quote) {
384 buffer.append('"');
385 }
386 for (int i = 0; i < value.length(); i++) {
387 final char ch = value.charAt(i);
388 if (isUnsafe(ch)) {
389 buffer.append('\\');
390 }
391 buffer.append(ch);
392 }
393 if (quote) {
394 buffer.append('"');
395 }
396 }
397
398
399 /**
400 * Checks whether a character is a {@link #SEPARATORS separator}.
401 *
402 * @param ch the character to check
403 *
404 * @return <code>true</code> if the character is a separator,
405 * <code>false</code> otherwise
406 */
407 protected boolean isSeparator(final char ch) {
408 return SEPARATORS.indexOf(ch) >= 0;
409 }
410
411
412 /**
413 * Checks whether a character is {@link #UNSAFE_CHARS unsafe}.
414 *
415 * @param ch the character to check
416 *
417 * @return <code>true</code> if the character is unsafe,
418 * <code>false</code> otherwise
419 */
420 protected boolean isUnsafe(final char ch) {
421 return UNSAFE_CHARS.indexOf(ch) >= 0;
422 }
423
424
425 } // class BasicHeaderValueFormatter