1 /*
2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HeaderElement.java,v 1.18 2003/04/14 04:06:55 mbecke Exp $
3 * $Revision: 1.18 $
4 * $Date: 2003/04/14 04:06:55 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * [Additional notices, if required by prior licensing conditions]
61 *
62 */
63
64 package org.apache.commons.httpclient;
65
66 import org.apache.commons.logging.Log;
67 import org.apache.commons.logging.LogFactory;
68
69 import java.util.BitSet;
70 import java.util.NoSuchElementException;
71 import java.util.StringTokenizer;
72 import java.util.Vector;
73
74
75 /***
76 * <p>One element of an HTTP header's value.</p>
77 * <p>
78 * Some HTTP headers (such as the set-cookie header) have values that
79 * can be decomposed into multiple elements. Such headers must be in the
80 * following form:
81 * </p>
82 * <pre>
83 * header = [ element ] *( "," [ element ] )
84 * element = name [ "=" [ value ] ] *( ";" [ param ] )
85 * param = name [ "=" [ value ] ]
86 *
87 * name = token
88 * value = ( token | quoted-string )
89 *
90 * token = 1*<any char except "=", ",", ";", <"> and
91 * white space>
92 * quoted-string = <"> *( text | quoted-char ) <">
93 * text = any char except <">
94 * quoted-char = "\" char
95 * </pre>
96 * <p>
97 * Any amount of white space is allowed between any part of the
98 * header, element or param and is ignored. A missing value in any
99 * element or param will be stored as the empty {@link String};
100 * if the "=" is also missing <var>null</var> will be stored instead.
101 * </p>
102 * <p>
103 * This class represents an individual header element, containing
104 * both a name/value pair (value may be <tt>null</tt>) and optionally
105 * a set of additional parameters.
106 * </p>
107 * <p>
108 * This class also exposes a {@link #parse} method for parsing a
109 * {@link Header} value into an array of elements.
110 * </p>
111 *
112 * @see Header
113 *
114 * @author <a href="mailto:bcholmes@interlog.com">B.C. Holmes</a>
115 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
116 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
117 *
118 * @since 1.0
119 * @version $Revision: 1.18 $ $Date: 2003/04/14 04:06:55 $
120 */
121 public class HeaderElement extends NameValuePair {
122
123 // ----------------------------------------------------------- Constructors
124
125 /***
126 * Default constructor.
127 */
128 public HeaderElement() {
129 this(null, null, null);
130 }
131
132 /***
133 * Constructor.
134 * @param name my name
135 * @param value my (possibly <tt>null</tt>) value
136 */
137 public HeaderElement(String name, String value) {
138 this(name, value, null);
139 }
140
141 /***
142 * Constructor with name, value and parameters.
143 *
144 * @param name my name
145 * @param value my (possibly <tt>null</tt>) value
146 * @param parameters my (possibly <tt>null</tt>) parameters
147 */
148 public HeaderElement(String name, String value,
149 NameValuePair[] parameters) {
150 super(name, value);
151 setParameters(parameters);
152 }
153
154 // -------------------------------------------------------- Constants
155
156 /*** Log object for this class. */
157 private static final Log LOG = LogFactory.getLog(HeaderElement.class);
158
159 /***
160 * Map of numeric values to whether or not the
161 * corresponding character is a "separator
162 * character" (tspecial).
163 */
164 private static final BitSet SEPARATORS = new BitSet(128);
165
166 /***
167 * Map of numeric values to whether or not the
168 * corresponding character is a "token
169 * character".
170 */
171 private static final BitSet TOKEN_CHAR = new BitSet(128);
172
173 /***
174 * Map of numeric values to whether or not the
175 * corresponding character is an "unsafe
176 * character".
177 */
178 private static final BitSet UNSAFE_CHAR = new BitSet(128);
179
180 /***
181 * Static initializer for {@link #SEPARATORS},
182 * {@link #TOKEN_CHAR}, and {@link #UNSAFE_CHAR}.
183 */
184 static {
185 // rfc-2068 tspecial
186 SEPARATORS.set('(');
187 SEPARATORS.set(')');
188 SEPARATORS.set('<');
189 SEPARATORS.set('>');
190 SEPARATORS.set('@');
191 SEPARATORS.set(',');
192 SEPARATORS.set(';');
193 SEPARATORS.set(':');
194 SEPARATORS.set('//');
195 SEPARATORS.set('"');
196 SEPARATORS.set('/');
197 SEPARATORS.set('[');
198 SEPARATORS.set(']');
199 SEPARATORS.set('?');
200 SEPARATORS.set('=');
201 SEPARATORS.set('{');
202 SEPARATORS.set('}');
203 SEPARATORS.set(' ');
204 SEPARATORS.set('\t');
205
206 // rfc-2068 token
207 for (int ch = 32; ch < 127; ch++) {
208 TOKEN_CHAR.set(ch);
209 }
210 TOKEN_CHAR.xor(SEPARATORS);
211
212 // rfc-1738 unsafe characters, including CTL and SP, and excluding
213 // "#" and "%"
214 for (int ch = 0; ch < 32; ch++) {
215 UNSAFE_CHAR.set(ch);
216 }
217 UNSAFE_CHAR.set(' ');
218 UNSAFE_CHAR.set('<');
219 UNSAFE_CHAR.set('>');
220 UNSAFE_CHAR.set('"');
221 UNSAFE_CHAR.set('{');
222 UNSAFE_CHAR.set('}');
223 UNSAFE_CHAR.set('|');
224 UNSAFE_CHAR.set('//');
225 UNSAFE_CHAR.set('^');
226 UNSAFE_CHAR.set('~');
227 UNSAFE_CHAR.set('[');
228 UNSAFE_CHAR.set(']');
229 UNSAFE_CHAR.set('`');
230 UNSAFE_CHAR.set(127);
231 }
232
233 // ----------------------------------------------------- Instance Variables
234
235 /*** My parameters, if any. */
236 private NameValuePair[] parameters = null;
237
238 // ------------------------------------------------------------- Properties
239
240 /***
241 * Get parameters, if any.
242 *
243 * @since 2.0
244 * @return parameters as an array of {@link NameValuePair}s
245 */
246 public NameValuePair[] getParameters() {
247 return this.parameters;
248 }
249
250 /***
251 *
252 * @param pairs The new parameters. May be null.
253 */
254 protected void setParameters(final NameValuePair[] pairs) {
255 parameters = pairs;
256 }
257 // --------------------------------------------------------- Public Methods
258
259 /***
260 * This parses the value part of a header. The result is an array of
261 * HeaderElement objects.
262 *
263 * @param headerValue the string representation of the header value
264 * (as received from the web server).
265 * @return the header elements containing <code>Header</code> elements.
266 * @throws HttpException if the above syntax rules are violated.
267 */
268 public static final HeaderElement[] parse(String headerValue)
269 throws HttpException {
270
271 LOG.trace("enter HeaderElement.parse(String)");
272
273 if (headerValue == null) {
274 return null;
275 }
276
277 Vector elements = new Vector();
278 StringTokenizer tokenizer =
279 new StringTokenizer(headerValue.trim(), ",");
280
281 while (tokenizer.countTokens() > 0) {
282 String nextToken = tokenizer.nextToken();
283
284 // FIXME: refactor into private method named ?
285 // careful... there may have been a comma in a quoted string
286 try {
287 while (HeaderElement.hasOddNumberOfQuotationMarks(nextToken)) {
288 nextToken += "," + tokenizer.nextToken();
289 }
290 } catch (NoSuchElementException exception) {
291 throw new HttpException(
292 "Bad header format: wrong number of quotation marks");
293 }
294
295 // FIXME: Refactor out into a private method named ?
296 try {
297 /*
298 * Following to RFC 2109 and 2965, in order not to conflict
299 * with the next header element, make it sure to parse tokens.
300 * the expires date format is "Wdy, DD-Mon-YY HH:MM:SS GMT".
301 * Notice that there is always comma(',') sign.
302 * For the general cases, rfc1123-date, rfc850-date.
303 */
304 if (tokenizer.hasMoreTokens()) {
305 String s = nextToken.toLowerCase();
306 if (s.endsWith("mon")
307 || s.endsWith("tue")
308 || s.endsWith("wed")
309 || s.endsWith("thu")
310 || s.endsWith("fri")
311 || s.endsWith("sat")
312 || s.endsWith("sun")
313 || s.endsWith("monday")
314 || s.endsWith("tuesday")
315 || s.endsWith("wednesday")
316 || s.endsWith("thursday")
317 || s.endsWith("friday")
318 || s.endsWith("saturday")
319 || s.endsWith("sunday")) {
320
321 nextToken += "," + tokenizer.nextToken();
322 }
323 }
324 } catch (NoSuchElementException exception) {
325 throw new HttpException
326 ("Bad header format: parsing with wrong header elements");
327 }
328
329 String tmp = nextToken.trim();
330 if (!tmp.endsWith(";")) {
331 tmp += ";";
332 }
333 char[] header = tmp.toCharArray();
334
335 // FIXME: refactor into a private method named? parseElement?
336 boolean inAString = false;
337 int startPos = 0;
338 HeaderElement element = new HeaderElement();
339 Vector paramlist = new Vector();
340 for (int i = 0 ; i < header.length ; i++) {
341 if (header[i] == ';' && !inAString) {
342 NameValuePair pair = parsePair(header, startPos, i);
343 if (pair == null) {
344 throw new HttpException(
345 "Bad header format: empty name/value pair in"
346 + nextToken);
347
348 // the first name/value pair are handled differently
349 } else if (startPos == 0) {
350 element.setName(pair.getName());
351 element.setValue(pair.getValue());
352 } else {
353 paramlist.addElement(pair);
354 }
355 startPos = i + 1;
356 } else if (header[i] == '"'
357 && !(inAString && i > 0 && header[i - 1] == '//')) {
358 inAString = !inAString;
359 }
360 }
361
362 // now let's add all the parameters into the header element
363 if (paramlist.size() > 0) {
364 NameValuePair[] tmp2 = new NameValuePair[paramlist.size()];
365 paramlist.copyInto((NameValuePair[]) tmp2);
366 element.setParameters (tmp2);
367 paramlist.removeAllElements();
368 }
369
370 // and save the header element into the list of header elements
371 elements.addElement(element);
372 }
373
374 HeaderElement[] headerElements = new HeaderElement[elements.size()];
375 elements.copyInto((HeaderElement[]) headerElements);
376 return headerElements;
377 }
378
379 /***
380 * Return <tt>true</tt> if <i>string</i> has
381 * an odd number of <tt>"</tt> characters.
382 *
383 * @param string the string to test
384 * @return true if there are an odd number of quotation marks, false
385 * otherwise
386 */
387 private static final boolean hasOddNumberOfQuotationMarks(String string) {
388 boolean odd = false;
389 int start = -1;
390 while ((start = string.indexOf('"', start + 1)) != -1) {
391 odd = !odd;
392 }
393 return odd;
394 }
395
396 /***
397 * Parse a header character array into a {@link NameValuePair}
398 *
399 * @param header the character array to parse
400 * @param start the starting position of the text within the array
401 * @param end the end position of the text within the array
402 * @return a {@link NameValuePair} representing the header
403 */
404 private static final NameValuePair parsePair(char[] header,
405 int start, int end) {
406
407 LOG.trace("enter HeaderElement.parsePair(char[], int, int)");
408
409 NameValuePair pair = null;
410 String name = new String(header, start, end - start).trim();
411 String value = null;
412
413 //TODO: This would certainly benefit from a StringBuffer
414 int index = name.indexOf("=");
415 if (index >= 0) {
416 if ((index + 1) < name.length()) {
417 value = name.substring(index + 1).trim();
418 // strip quotation marks
419 if (value.startsWith("\"") && value.endsWith("\"")) {
420 value = value.substring(1, value.length() - 1);
421 }
422 }
423 name = name.substring(0, index).trim();
424 }
425
426 pair = new NameValuePair(name, value);
427
428 return pair;
429 }
430
431
432 /***
433 * Returns parameter with the given name, if found. Otherwise null
434 * is returned
435 *
436 * @param name The name to search by.
437 * @return NameValuePair parameter with the given name
438 */
439
440 public NameValuePair getParameterByName(String name) {
441 if (name == null) {
442 throw new NullPointerException("Name is null");
443 }
444 NameValuePair found = null;
445 NameValuePair parameters[] = getParameters();
446 if (parameters != null) {
447 for (int i = 0; i < parameters.length; i++) {
448 NameValuePair current = parameters[ i ];
449 if (current.getName().equalsIgnoreCase(name)) {
450 found = current;
451 break;
452 }
453 }
454 }
455 return found;
456 }
457
458
459 }
460
This page was automatically generated by Maven