View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HeaderElement.java,v 1.18.2.1 2004/02/22 18:21:13 olegk Exp $
3    * $Revision: 1.18.2.1 $
4    * $Date: 2004/02/22 18:21:13 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   * [Additional notices, if required by prior licensing conditions]
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*&lt;any char except "=", ",", ";", &lt;"&gt; and
59   *                       white space&gt;
60   * quoted-string = &lt;"&gt; *( text | quoted-char ) &lt;"&gt;
61   * text          = any char except &lt;"&gt;
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      // ----------------------------------------------------------- Constructors
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     // -------------------------------------------------------- Constants
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         // rfc-2068 tspecial
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         // rfc-2068 token
175         for (int ch = 32; ch < 127; ch++) {
176             TOKEN_CHAR.set(ch);
177         }
178         TOKEN_CHAR.xor(SEPARATORS);
179 
180         // rfc-1738 unsafe characters, including CTL and SP, and excluding
181         // "#" and "%"
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     // ----------------------------------------------------- Instance Variables
202 
203     /*** My parameters, if any. */
204     private NameValuePair[] parameters = null;
205 
206     // ------------------------------------------------------------- Properties
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     // --------------------------------------------------------- Public Methods
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             // FIXME: refactor into private method named ?
253             // careful... there may have been a comma in a quoted string
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             // FIXME: Refactor out into a private method named ?
264             try {
265                 /*
266                  * Following to RFC 2109 and 2965, in order not to conflict
267                  * with the next header element, make it sure to parse tokens.
268                  * the expires date format is "Wdy, DD-Mon-YY HH:MM:SS GMT".
269                  * Notice that there is always comma(',') sign.
270                  * For the general cases, rfc1123-date, rfc850-date.
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             // FIXME: refactor into a private method named? parseElement?
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                     // the first name/value pair are handled differently
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             // now let's add all the parameters into the header element
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             // and save the header element into the list of header elements
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         //TODO: This would certainly benefit from a StringBuffer
382         int index = name.indexOf("=");
383         if (index >= 0) {
384             if ((index + 1) < name.length()) {
385                 value = name.substring(index + 1).trim();
386                 // strip quotation marks
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