1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.apache.commons.httpclient;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import java.util.BitSet;
38 import java.util.NoSuchElementException;
39 import java.util.StringTokenizer;
40 import java.util.Vector;
41
42
43 /***
44 * <p>One element of an HTTP header's value.</p>
45 * <p>
46 * Some HTTP headers (such as the set-cookie header) have values that
47 * can be decomposed into multiple elements. Such headers must be in the
48 * following form:
49 * </p>
50 * <pre>
51 * header = [ element ] *( "," [ element ] )
52 * element = name [ "=" [ value ] ] *( ";" [ param ] )
53 * param = name [ "=" [ value ] ]
54 *
55 * name = token
56 * value = ( token | quoted-string )
57 *
58 * token = 1*<any char except "=", ",", ";", <"> and
59 * white space>
60 * quoted-string = <"> *( text | quoted-char ) <">
61 * text = any char except <">
62 * quoted-char = "\" char
63 * </pre>
64 * <p>
65 * Any amount of white space is allowed between any part of the
66 * header, element or param and is ignored. A missing value in any
67 * element or param will be stored as the empty {@link String};
68 * if the "=" is also missing <var>null</var> will be stored instead.
69 * </p>
70 * <p>
71 * This class represents an individual header element, containing
72 * both a name/value pair (value may be <tt>null</tt>) and optionally
73 * a set of additional parameters.
74 * </p>
75 * <p>
76 * This class also exposes a {@link #parse} method for parsing a
77 * {@link Header} value into an array of elements.
78 * </p>
79 *
80 * @see Header
81 *
82 * @author <a href="mailto:bcholmes@interlog.com">B.C. Holmes</a>
83 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
84 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
85 *
86 * @since 1.0
87 * @version $Revision: 1.18.2.1 $ $Date: 2004/02/22 18:21:13 $
88 */
89 public class HeaderElement extends NameValuePair {
90
91
92
93 /***
94 * Default constructor.
95 */
96 public HeaderElement() {
97 this(null, null, null);
98 }
99
100 /***
101 * Constructor.
102 * @param name my name
103 * @param value my (possibly <tt>null</tt>) value
104 */
105 public HeaderElement(String name, String value) {
106 this(name, value, null);
107 }
108
109 /***
110 * Constructor with name, value and parameters.
111 *
112 * @param name my name
113 * @param value my (possibly <tt>null</tt>) value
114 * @param parameters my (possibly <tt>null</tt>) parameters
115 */
116 public HeaderElement(String name, String value,
117 NameValuePair[] parameters) {
118 super(name, value);
119 setParameters(parameters);
120 }
121
122
123
124 /*** Log object for this class. */
125 private static final Log LOG = LogFactory.getLog(HeaderElement.class);
126
127 /***
128 * Map of numeric values to whether or not the
129 * corresponding character is a "separator
130 * character" (tspecial).
131 */
132 private static final BitSet SEPARATORS = new BitSet(128);
133
134 /***
135 * Map of numeric values to whether or not the
136 * corresponding character is a "token
137 * character".
138 */
139 private static final BitSet TOKEN_CHAR = new BitSet(128);
140
141 /***
142 * Map of numeric values to whether or not the
143 * corresponding character is an "unsafe
144 * character".
145 */
146 private static final BitSet UNSAFE_CHAR = new BitSet(128);
147
148 /***
149 * Static initializer for {@link #SEPARATORS},
150 * {@link #TOKEN_CHAR}, and {@link #UNSAFE_CHAR}.
151 */
152 static {
153
154 SEPARATORS.set('(');
155 SEPARATORS.set(')');
156 SEPARATORS.set('<');
157 SEPARATORS.set('>');
158 SEPARATORS.set('@');
159 SEPARATORS.set(',');
160 SEPARATORS.set(';');
161 SEPARATORS.set(':');
162 SEPARATORS.set('//');
163 SEPARATORS.set('"');
164 SEPARATORS.set('/');
165 SEPARATORS.set('[');
166 SEPARATORS.set(']');
167 SEPARATORS.set('?');
168 SEPARATORS.set('=');
169 SEPARATORS.set('{');
170 SEPARATORS.set('}');
171 SEPARATORS.set(' ');
172 SEPARATORS.set('\t');
173
174
175 for (int ch = 32; ch < 127; ch++) {
176 TOKEN_CHAR.set(ch);
177 }
178 TOKEN_CHAR.xor(SEPARATORS);
179
180
181
182 for (int ch = 0; ch < 32; ch++) {
183 UNSAFE_CHAR.set(ch);
184 }
185 UNSAFE_CHAR.set(' ');
186 UNSAFE_CHAR.set('<');
187 UNSAFE_CHAR.set('>');
188 UNSAFE_CHAR.set('"');
189 UNSAFE_CHAR.set('{');
190 UNSAFE_CHAR.set('}');
191 UNSAFE_CHAR.set('|');
192 UNSAFE_CHAR.set('//');
193 UNSAFE_CHAR.set('^');
194 UNSAFE_CHAR.set('~');
195 UNSAFE_CHAR.set('[');
196 UNSAFE_CHAR.set(']');
197 UNSAFE_CHAR.set('`');
198 UNSAFE_CHAR.set(127);
199 }
200
201
202
203 /*** My parameters, if any. */
204 private NameValuePair[] parameters = null;
205
206
207
208 /***
209 * Get parameters, if any.
210 *
211 * @since 2.0
212 * @return parameters as an array of {@link NameValuePair}s
213 */
214 public NameValuePair[] getParameters() {
215 return this.parameters;
216 }
217
218 /***
219 *
220 * @param pairs The new parameters. May be null.
221 */
222 protected void setParameters(final NameValuePair[] pairs) {
223 parameters = pairs;
224 }
225
226
227 /***
228 * This parses the value part of a header. The result is an array of
229 * HeaderElement objects.
230 *
231 * @param headerValue the string representation of the header value
232 * (as received from the web server).
233 * @return the header elements containing <code>Header</code> elements.
234 * @throws HttpException if the above syntax rules are violated.
235 */
236 public static final HeaderElement[] parse(String headerValue)
237 throws HttpException {
238
239 LOG.trace("enter HeaderElement.parse(String)");
240
241 if (headerValue == null) {
242 return null;
243 }
244
245 Vector elements = new Vector();
246 StringTokenizer tokenizer =
247 new StringTokenizer(headerValue.trim(), ",");
248
249 while (tokenizer.countTokens() > 0) {
250 String nextToken = tokenizer.nextToken();
251
252
253
254 try {
255 while (HeaderElement.hasOddNumberOfQuotationMarks(nextToken)) {
256 nextToken += "," + tokenizer.nextToken();
257 }
258 } catch (NoSuchElementException exception) {
259 throw new HttpException(
260 "Bad header format: wrong number of quotation marks");
261 }
262
263
264 try {
265
266
267
268
269
270
271
272 if (tokenizer.hasMoreTokens()) {
273 String s = nextToken.toLowerCase();
274 if (s.endsWith("mon")
275 || s.endsWith("tue")
276 || s.endsWith("wed")
277 || s.endsWith("thu")
278 || s.endsWith("fri")
279 || s.endsWith("sat")
280 || s.endsWith("sun")
281 || s.endsWith("monday")
282 || s.endsWith("tuesday")
283 || s.endsWith("wednesday")
284 || s.endsWith("thursday")
285 || s.endsWith("friday")
286 || s.endsWith("saturday")
287 || s.endsWith("sunday")) {
288
289 nextToken += "," + tokenizer.nextToken();
290 }
291 }
292 } catch (NoSuchElementException exception) {
293 throw new HttpException
294 ("Bad header format: parsing with wrong header elements");
295 }
296
297 String tmp = nextToken.trim();
298 if (!tmp.endsWith(";")) {
299 tmp += ";";
300 }
301 char[] header = tmp.toCharArray();
302
303
304 boolean inAString = false;
305 int startPos = 0;
306 HeaderElement element = new HeaderElement();
307 Vector paramlist = new Vector();
308 for (int i = 0 ; i < header.length ; i++) {
309 if (header[i] == ';' && !inAString) {
310 NameValuePair pair = parsePair(header, startPos, i);
311 if (pair == null) {
312 throw new HttpException(
313 "Bad header format: empty name/value pair in"
314 + nextToken);
315
316
317 } else if (startPos == 0) {
318 element.setName(pair.getName());
319 element.setValue(pair.getValue());
320 } else {
321 paramlist.addElement(pair);
322 }
323 startPos = i + 1;
324 } else if (header[i] == '"'
325 && !(inAString && i > 0 && header[i - 1] == '//')) {
326 inAString = !inAString;
327 }
328 }
329
330
331 if (paramlist.size() > 0) {
332 NameValuePair[] tmp2 = new NameValuePair[paramlist.size()];
333 paramlist.copyInto((NameValuePair[]) tmp2);
334 element.setParameters (tmp2);
335 paramlist.removeAllElements();
336 }
337
338
339 elements.addElement(element);
340 }
341
342 HeaderElement[] headerElements = new HeaderElement[elements.size()];
343 elements.copyInto((HeaderElement[]) headerElements);
344 return headerElements;
345 }
346
347 /***
348 * Return <tt>true</tt> if <i>string</i> has
349 * an odd number of <tt>"</tt> characters.
350 *
351 * @param string the string to test
352 * @return true if there are an odd number of quotation marks, false
353 * otherwise
354 */
355 private static final boolean hasOddNumberOfQuotationMarks(String string) {
356 boolean odd = false;
357 int start = -1;
358 while ((start = string.indexOf('"', start + 1)) != -1) {
359 odd = !odd;
360 }
361 return odd;
362 }
363
364 /***
365 * Parse a header character array into a {@link NameValuePair}
366 *
367 * @param header the character array to parse
368 * @param start the starting position of the text within the array
369 * @param end the end position of the text within the array
370 * @return a {@link NameValuePair} representing the header
371 */
372 private static final NameValuePair parsePair(char[] header,
373 int start, int end) {
374
375 LOG.trace("enter HeaderElement.parsePair(char[], int, int)");
376
377 NameValuePair pair = null;
378 String name = new String(header, start, end - start).trim();
379 String value = null;
380
381
382 int index = name.indexOf("=");
383 if (index >= 0) {
384 if ((index + 1) < name.length()) {
385 value = name.substring(index + 1).trim();
386
387 if (value.startsWith("\"") && value.endsWith("\"")) {
388 value = value.substring(1, value.length() - 1);
389 }
390 }
391 name = name.substring(0, index).trim();
392 }
393
394 pair = new NameValuePair(name, value);
395
396 return pair;
397 }
398
399
400 /***
401 * Returns parameter with the given name, if found. Otherwise null
402 * is returned
403 *
404 * @param name The name to search by.
405 * @return NameValuePair parameter with the given name
406 */
407
408 public NameValuePair getParameterByName(String name) {
409 if (name == null) {
410 throw new NullPointerException("Name is null");
411 }
412 NameValuePair found = null;
413 NameValuePair parameters[] = getParameters();
414 if (parameters != null) {
415 for (int i = 0; i < parameters.length; i++) {
416 NameValuePair current = parameters[ i ];
417 if (current.getName().equalsIgnoreCase(name)) {
418 found = current;
419 break;
420 }
421 }
422 }
423 return found;
424 }
425
426
427 }
428