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