View Javadoc
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