Index: java/org/apache/commons/httpclient/HeaderElement.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HeaderElement.java,v retrieving revision 1.18 diff -u -r1.18 HeaderElement.java --- java/org/apache/commons/httpclient/HeaderElement.java 14 Apr 2003 04:06:55 -0000 1.18 +++ java/org/apache/commons/httpclient/HeaderElement.java 12 Jul 2003 14:35:52 -0000 @@ -63,15 +63,13 @@ package org.apache.commons.httpclient; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.httpclient.util.ParameterParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import java.util.BitSet; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; -import java.util.Vector; - - /** *

One element of an HTTP header's value.

*

@@ -114,6 +112,7 @@ * @author B.C. Holmes * @author Park, Sung-Gu * @author Mike Bowler + * @author Oleg Kalnichevski * * @since 1.0 * @version $Revision: 1.18 $ $Date: 2003/04/14 04:06:55 $ @@ -148,87 +147,47 @@ public HeaderElement(String name, String value, NameValuePair[] parameters) { super(name, value); - setParameters(parameters); + this.parameters = parameters; } - // -------------------------------------------------------- Constants - - /** Log object for this class. */ - private static final Log LOG = LogFactory.getLog(HeaderElement.class); - /** - * Map of numeric values to whether or not the - * corresponding character is a "separator - * character" (tspecial). - */ - private static final BitSet SEPARATORS = new BitSet(128); - - /** - * Map of numeric values to whether or not the - * corresponding character is a "token - * character". - */ - private static final BitSet TOKEN_CHAR = new BitSet(128); + * Constructor with array of characters. + * + * @param chars the array of characters + * @param offset - the initial offset. + * @param length - the length. + */ + public HeaderElement(char[] chars, int offset, int length) { + this(); + if (chars == null) { + return; + } + ParameterParser parser = new ParameterParser(); + List params = parser.parse(chars, offset, length, ';'); + if (params.size() > 0) { + NameValuePair element = (NameValuePair)params.remove(0); + setName(element.getName()); + setValue(element.getValue()); + if (params.size() > 0) { + this.parameters = (NameValuePair[]) + params.toArray(new NameValuePair[params.size()]); + } + } + } /** - * Map of numeric values to whether or not the - * corresponding character is an "unsafe - * character". + * Constructor with array of characters. + * + * @param chars the array of characters */ - private static final BitSet UNSAFE_CHAR = new BitSet(128); + public HeaderElement(char[] chars) { + this(chars, 0, chars.length); + } - /** - * Static initializer for {@link #SEPARATORS}, - * {@link #TOKEN_CHAR}, and {@link #UNSAFE_CHAR}. - */ - static { - // rfc-2068 tspecial - SEPARATORS.set('('); - SEPARATORS.set(')'); - SEPARATORS.set('<'); - SEPARATORS.set('>'); - SEPARATORS.set('@'); - SEPARATORS.set(','); - SEPARATORS.set(';'); - SEPARATORS.set(':'); - SEPARATORS.set('\\'); - SEPARATORS.set('"'); - SEPARATORS.set('/'); - SEPARATORS.set('['); - SEPARATORS.set(']'); - SEPARATORS.set('?'); - SEPARATORS.set('='); - SEPARATORS.set('{'); - SEPARATORS.set('}'); - SEPARATORS.set(' '); - SEPARATORS.set('\t'); - - // rfc-2068 token - for (int ch = 32; ch < 127; ch++) { - TOKEN_CHAR.set(ch); - } - TOKEN_CHAR.xor(SEPARATORS); + // -------------------------------------------------------- Constants - // rfc-1738 unsafe characters, including CTL and SP, and excluding - // "#" and "%" - for (int ch = 0; ch < 32; ch++) { - UNSAFE_CHAR.set(ch); - } - UNSAFE_CHAR.set(' '); - UNSAFE_CHAR.set('<'); - UNSAFE_CHAR.set('>'); - UNSAFE_CHAR.set('"'); - UNSAFE_CHAR.set('{'); - UNSAFE_CHAR.set('}'); - UNSAFE_CHAR.set('|'); - UNSAFE_CHAR.set('\\'); - UNSAFE_CHAR.set('^'); - UNSAFE_CHAR.set('~'); - UNSAFE_CHAR.set('['); - UNSAFE_CHAR.set(']'); - UNSAFE_CHAR.set('`'); - UNSAFE_CHAR.set(127); - } + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(HeaderElement.class); // ----------------------------------------------------- Instance Variables @@ -247,187 +206,91 @@ return this.parameters; } - /** - * - * @param pairs The new parameters. May be null. - */ - protected void setParameters(final NameValuePair[] pairs) { - parameters = pairs; - } // --------------------------------------------------------- Public Methods /** * This parses the value part of a header. The result is an array of * HeaderElement objects. * - * @param headerValue the string representation of the header value + * @param headerValue the array of char representation of the header value * (as received from the web server). - * @return the header elements containing Header elements. - * @throws HttpException if the above syntax rules are violated. + * @return array of {@link HeaderElement}s. */ - public static final HeaderElement[] parse(String headerValue) - throws HttpException { + public static final HeaderElement[] parseElements(char[] headerValue) { - LOG.trace("enter HeaderElement.parse(String)"); + LOG.trace("enter HeaderElement.parseElements(char[])"); if (headerValue == null) { return null; } + List elements = new ArrayList(); - Vector elements = new Vector(); - StringTokenizer tokenizer = - new StringTokenizer(headerValue.trim(), ","); - - while (tokenizer.countTokens() > 0) { - String nextToken = tokenizer.nextToken(); - - // FIXME: refactor into private method named ? - // careful... there may have been a comma in a quoted string - try { - while (HeaderElement.hasOddNumberOfQuotationMarks(nextToken)) { - nextToken += "," + tokenizer.nextToken(); - } - } catch (NoSuchElementException exception) { - throw new HttpException( - "Bad header format: wrong number of quotation marks"); - } - - // FIXME: Refactor out into a private method named ? - try { - /* - * Following to RFC 2109 and 2965, in order not to conflict - * with the next header element, make it sure to parse tokens. - * the expires date format is "Wdy, DD-Mon-YY HH:MM:SS GMT". - * Notice that there is always comma(',') sign. - * For the general cases, rfc1123-date, rfc850-date. - */ - if (tokenizer.hasMoreTokens()) { - String s = nextToken.toLowerCase(); - if (s.endsWith("mon") - || s.endsWith("tue") - || s.endsWith("wed") - || s.endsWith("thu") - || s.endsWith("fri") - || s.endsWith("sat") - || s.endsWith("sun") - || s.endsWith("monday") - || s.endsWith("tuesday") - || s.endsWith("wednesday") - || s.endsWith("thursday") - || s.endsWith("friday") - || s.endsWith("saturday") - || s.endsWith("sunday")) { - - nextToken += "," + tokenizer.nextToken(); - } - } - } catch (NoSuchElementException exception) { - throw new HttpException - ("Bad header format: parsing with wrong header elements"); - } - - String tmp = nextToken.trim(); - if (!tmp.endsWith(";")) { - tmp += ";"; + int i = 0; + int from = 0; + int len = headerValue.length; + boolean qouted = false; + while(i < len) { + char ch = headerValue[i]; + if (ch == '"') { + qouted = !qouted; } - char[] header = tmp.toCharArray(); - - // FIXME: refactor into a private method named? parseElement? - boolean inAString = false; - int startPos = 0; - HeaderElement element = new HeaderElement(); - Vector paramlist = new Vector(); - for (int i = 0 ; i < header.length ; i++) { - if (header[i] == ';' && !inAString) { - NameValuePair pair = parsePair(header, startPos, i); - if (pair == null) { - throw new HttpException( - "Bad header format: empty name/value pair in" - + nextToken); - - // the first name/value pair are handled differently - } else if (startPos == 0) { - element.setName(pair.getName()); - element.setValue(pair.getValue()); - } else { - paramlist.addElement(pair); - } - startPos = i + 1; - } else if (header[i] == '"' - && !(inAString && i > 0 && header[i - 1] == '\\')) { - inAString = !inAString; - } + HeaderElement element = null; + if ((!qouted) && (ch == ',')) { + element = new HeaderElement(headerValue, from, i); + from = i + 1; + } else if (i == len - 1) { + element = new HeaderElement(headerValue, from, len); } - - // now let's add all the parameters into the header element - if (paramlist.size() > 0) { - NameValuePair[] tmp2 = new NameValuePair[paramlist.size()]; - paramlist.copyInto((NameValuePair[]) tmp2); - element.setParameters (tmp2); - paramlist.removeAllElements(); + if ((element != null) && (element.getName() != null)) { + elements.add(element); } - - // and save the header element into the list of header elements - elements.addElement(element); + i++; } - - HeaderElement[] headerElements = new HeaderElement[elements.size()]; - elements.copyInto((HeaderElement[]) headerElements); - return headerElements; + return (HeaderElement[]) + elements.toArray(new HeaderElement[elements.size()]); } /** - * Return true if string has - * an odd number of " characters. + * This parses the value part of a header. The result is an array of + * HeaderElement objects. * - * @param string the string to test - * @return true if there are an odd number of quotation marks, false - * otherwise + * @param headerValue the string representation of the header value + * (as received from the web server). + * @return array of {@link HeaderElement}s. + * @throws HttpException if the above syntax rules are violated. */ - private static final boolean hasOddNumberOfQuotationMarks(String string) { - boolean odd = false; - int start = -1; - while ((start = string.indexOf('"', start + 1)) != -1) { - odd = !odd; + public static final HeaderElement[] parseElements(String headerValue) { + + LOG.trace("enter HeaderElement.parseElements(String)"); + + if (headerValue == null) { + return null; } - return odd; + return parseElements(headerValue.toCharArray()); } /** - * Parse a header character array into a {@link NameValuePair} + * This parses the value part of a header. The result is an array of + * HeaderElement objects. * - * @param header the character array to parse - * @param start the starting position of the text within the array - * @param end the end position of the text within the array - * @return a {@link NameValuePair} representing the header + * @param headerValue the string representation of the header value + * (as received from the web server). + * @return array of {@link HeaderElement}s. + * @throws HttpException if the above syntax rules are violated. + * + * @deprecated Use #parseElements(String). */ - private static final NameValuePair parsePair(char[] header, - int start, int end) { + public static final HeaderElement[] parse(String headerValue) + throws HttpException { - LOG.trace("enter HeaderElement.parsePair(char[], int, int)"); + LOG.trace("enter HeaderElement.parse(String)"); - NameValuePair pair = null; - String name = new String(header, start, end - start).trim(); - String value = null; - - //TODO: This would certainly benefit from a StringBuffer - int index = name.indexOf("="); - if (index >= 0) { - if ((index + 1) < name.length()) { - value = name.substring(index + 1).trim(); - // strip quotation marks - if (value.startsWith("\"") && value.endsWith("\"")) { - value = value.substring(1, value.length() - 1); - } - } - name = name.substring(0, index).trim(); + if (headerValue == null) { + return null; } - - pair = new NameValuePair(name, value); - - return pair; + return parseElements(headerValue.toCharArray()); } - + /** * Returns parameter with the given name, if found. Otherwise null @@ -438,8 +301,11 @@ */ public NameValuePair getParameterByName(String name) { + + LOG.trace("enter HeaderElement.getParameterByName(String)"); + if (name == null) { - throw new NullPointerException("Name is null"); + throw new IllegalArgumentException("Name may not be null"); } NameValuePair found = null; NameValuePair parameters[] = getParameters(); @@ -454,7 +320,6 @@ } return found; } - } Index: java/org/apache/commons/httpclient/auth/AuthChallengeParser.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthChallengeParser.java,v retrieving revision 1.4 diff -u -r1.4 AuthChallengeParser.java --- java/org/apache/commons/httpclient/auth/AuthChallengeParser.java 6 Apr 2003 22:31:53 -0000 1.4 +++ java/org/apache/commons/httpclient/auth/AuthChallengeParser.java 12 Jul 2003 14:35:54 -0000 @@ -63,8 +63,12 @@ package org.apache.commons.httpclient.auth; -import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.util.ParameterParser; /** * This class provides utility methods for parsing HTTP www and proxy authentication @@ -92,12 +96,12 @@ if (challengeStr == null) { throw new IllegalArgumentException("Challenge may not be null"); } - int i = challengeStr.indexOf(' '); + int idx = challengeStr.indexOf(' '); String s = null; - if (i == -1) { + if (idx == -1) { s = challengeStr; } else { - s = challengeStr.substring(0, i); + s = challengeStr.substring(0, idx); } if (s.equals("")) { throw new MalformedChallengeException("Invalid challenge: " + challengeStr); @@ -121,114 +125,18 @@ if (challengeStr == null) { throw new IllegalArgumentException("Challenge may not be null"); } - int i = challengeStr.indexOf(' '); - if (i == -1) { + int idx = challengeStr.indexOf(' '); + if (idx == -1) { throw new MalformedChallengeException("Invalid challenge: " + challengeStr); } - - Map elements = new HashMap(); - - i++; - int len = challengeStr.length(); - - String name = null; - String value = null; - - StringBuffer buffer = new StringBuffer(); - - boolean parsingName = true; - boolean inQuote = false; - boolean gotIt = false; - - while (i < len) { - // Parse one char at a time - char ch = challengeStr.charAt(i); - i++; - // Process the char - if (parsingName) { - // parsing name - if (ch == '=') { - name = buffer.toString().trim(); - parsingName = false; - buffer.setLength(0); - } else if (ch == ',') { - name = buffer.toString().trim(); - value = null; - gotIt = true; - buffer.setLength(0); - } else { - buffer.append(ch); - } - // Have I reached the end of the challenge string? - if (i == len) { - name = buffer.toString().trim(); - value = null; - gotIt = true; - } - } else { - //parsing value - if (!inQuote) { - // Value is not quoted or not found yet - if (ch == ',') { - value = buffer.toString().trim(); - gotIt = true; - buffer.setLength(0); - } else { - // no value yet - if (buffer.length() == 0) { - if (ch == ' ') { - //discard - } else if (ch == '\t') { - //discard - } else if (ch == '\n') { - //discard - } else if (ch == '\r') { - //discard - } else { - // otherwise add to the buffer - buffer.append(ch); - if (ch == '"') { - inQuote = true; - } - } - } else { - // already got something - // just keep on adding to the buffer - buffer.append(ch); - } - } - } else { - // Value is quoted - // Keep on adding until closing quote is encountered - buffer.append(ch); - if (ch == '"') { - inQuote = false; - } - } - // Have I reached the end of the challenge string? - if (i == len) { - value = buffer.toString().trim(); - gotIt = true; - } - } - if (gotIt) { - // Got something - if ((name == null) || (name.equals(""))) { - throw new MalformedChallengeException("Invalid challenge: " + challengeStr); - } - // Strip quotes when present - if ((value != null) && (value.length() > 1)) { - if ((value.charAt(0) == '"') - && (value.charAt(value.length() - 1) == '"')) { - value = value.substring(1, value.length() - 1); - } - } - - elements.put(name, value); - parsingName = true; - gotIt = false; - } + Map map = new HashMap(); + ParameterParser parser = new ParameterParser(); + List params = parser.parse( + challengeStr.substring(idx + 1, challengeStr.length()), ','); + for (int i = 0; i < params.size(); i++) { + NameValuePair param = (NameValuePair)params.get(i); + map.put(param.getName().toLowerCase(), param.getValue()); } - return elements; + return map; } } Index: java/org/apache/commons/httpclient/cookie/CookieSpecBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java,v retrieving revision 1.16 diff -u -r1.16 CookieSpecBase.java --- java/org/apache/commons/httpclient/cookie/CookieSpecBase.java 12 Jun 2003 19:12:16 -0000 1.16 +++ java/org/apache/commons/httpclient/cookie/CookieSpecBase.java 12 Jul 2003 14:35:58 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java,v 1.16 2003/06/12 19:12:16 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java,v 1.16 2003/06/12 19:12:16 olegk Exp $ * $Revision: 1.16 $ * $Date: 2003/06/12 19:12:16 $ * @@ -70,7 +70,6 @@ import org.apache.commons.httpclient.Cookie; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HeaderElement; -import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.util.DateParseException; import org.apache.commons.httpclient.util.DateParser; @@ -166,14 +165,7 @@ path = PATH_DELIM; } host = host.toLowerCase(); - - HeaderElement[] headerElements = null; - try { - headerElements = HeaderElement.parse(header); - } catch (HttpException e) { - throw new MalformedCookieException(e.getMessage()); - } - + String defaultPath = path; int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM); if (lastSlashIndex >= 0) { @@ -183,6 +175,31 @@ } defaultPath = defaultPath.substring(0, lastSlashIndex); } + + HeaderElement[] headerElements = null; + + boolean isNetscapeCookie = false; + int i1 = header.toLowerCase().indexOf("expires="); + if (i1 != -1) { + i1 += "expires=".length(); + int i2 = header.indexOf(";", i1); + if (i2 == -1) { + i2 = header.length(); + } + try { + DateParser.parseDate(header.substring(i1, i2)); + isNetscapeCookie = true; + } catch(DateParseException e) { + // Does not look like a valid expiry date + } + } + if (isNetscapeCookie) { + headerElements = new HeaderElement[] { + new HeaderElement(header.toCharArray()) + }; + } else { + headerElements = HeaderElement.parseElements(header.toCharArray()); + } Cookie[] cookies = new Cookie[headerElements.length]; @@ -330,14 +347,6 @@ if (paramValue == null) { throw new MalformedCookieException( "Missing value for expires attribute"); - } - // trim single quotes around expiry if present - // see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279 - if (paramValue.length() > 1 - && paramValue.startsWith("'") - && paramValue.endsWith("'")) { - paramValue - = paramValue.substring (1, paramValue.length() - 1); } try { Index: java/org/apache/commons/httpclient/util/DateParser.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/util/DateParser.java,v retrieving revision 1.3 diff -u -r1.3 DateParser.java --- java/org/apache/commons/httpclient/util/DateParser.java 26 May 2003 21:51:37 -0000 1.3 +++ java/org/apache/commons/httpclient/util/DateParser.java 12 Jul 2003 14:35:59 -0000 @@ -144,6 +144,15 @@ if (dateValue == null) { throw new IllegalArgumentException("dateValue is null"); } + + // trim single quotes around date if present + // see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279 + if (dateValue.length() > 1 + && dateValue.startsWith("'") + && dateValue.endsWith("'")) + { + dateValue = dateValue.substring (1, dateValue.length() - 1); + } SimpleDateFormat dateParser = null; Index: java/org/apache/commons/httpclient/util/ParameterParser.java =================================================================== RCS file: java/org/apache/commons/httpclient/util/ParameterParser.java diff -N java/org/apache/commons/httpclient/util/ParameterParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/util/ParameterParser.java 12 Jul 2003 14:36:00 -0000 @@ -0,0 +1,261 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient.util; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.httpclient.NameValuePair; + +/** + * A simple parser intended to parse sequences of name/value pairs. + * Parameter values are exptected to be enclosed in quotes if they + * contain unsafe characters, such as '=' characters or separators. + * Parameter values are optional and can be omitted. + * + *

+ * param1 = value; param2 = "anything goes; really"; param3 + *

+ * + * @author Oleg Kalnichevski + */ + +public class ParameterParser { + + /** String to be parsed */ + private char[] chars = null; + + /** Current position in the string */ + private int pos = 0; + + /** Maximum position in the string */ + private int len = 0; + + /** Start of a token */ + private int i1 = 0; + + /** End of a token */ + private int i2 = 0; + + /** Default ParameterParser constructor */ + public ParameterParser() { + super(); + } + + + /** Are there any characters left to parse? */ + private boolean hasChar() { + return this.pos < this.len; + } + + + /** A helper method to process the parsed token. */ + private String getToken(boolean quoted) { + // Trim leading white spaces + while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) { + i1++; + } + // Trim trailing white spaces + while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) { + i2--; + } + // Strip away quotes if necessary + if (quoted) { + if (((i2 - i1) >= 2) && + (chars[i1] == '"') && + (chars[i2 - 1] == '"') ) { + i1++; + i2--; + } + } + String result = null; + if (i2 > i1) { + result = new String(chars, i1, i2 - i1); + } + return result; + } + + + /** Is given character present in the array of characters? */ + private boolean isOneOf(char ch, char[] charray) { + boolean result = false; + for (int i = 0; i < charray.length; i ++) { + if (ch == charray[i]) { + result = true; + break; + } + } + return result; + } + + + /** Parse out a token until any of the given terminators + * is encountered. */ + private String parseToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + while (hasChar()) { + ch = chars[pos]; + if (isOneOf(ch, terminators)) { + break; + } + i2++; + pos++; + } + return getToken(false); + } + + + /** Parse out a token until any of the given terminators + * is encountered. Special characters in quoted tokens + * are escaped. */ + private String parseQuotedToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + boolean quoted = false; + while (hasChar()) { + ch = chars[pos]; + if (!quoted && isOneOf(ch, terminators)) { + break; + } + if (ch == '"') { + quoted = !quoted; + } + i2++; + pos++; + } + return getToken(true); + } + + /** + * Extracts a list of {@link NameValuePair}s from the given string. + * + * @param str the string that contains a sequence of name/value pairs + * @return a list of {@link NameValuePair}s + * + */ + public List parse(final String str, char separator) { + + if (str == null) { + return null; + } + return parse(str.toCharArray(), separator); + } + + /** + * Extracts a list of {@link NameValuePair}s from the given array of + * characters. + * + * @param chars the array of characters that contains a sequence of + * name/value pairs + * + * @return a list of {@link NameValuePair}s + */ + public List parse(final char[] chars, char separator) { + + if (chars == null) { + return null; + } + return parse(chars, 0, chars.length, separator); + } + + + /** + * Extracts a list of {@link NameValuePair}s from the given array of + * characters. + * + * @param chars the array of characters that contains a sequence of + * name/value pairs + * @param offset - the initial offset. + * @param length - the length. + * + * @return a list of {@link NameValuePair}s + */ + public List parse(final char[] chars, int offset, int length, char separator) { + + if (chars == null) { + return null; + } + List params = new ArrayList(); + this.chars = chars; + this.pos = offset; + this.len = length; + + String paramName = null; + String paramValue = null; + boolean done = false; + while (hasChar()) { + paramName = parseToken(new char[] {'=', separator}); + paramValue = null; + if (hasChar() && (chars[pos] == '=')) { + pos++; // skip '=' + paramValue = parseQuotedToken(new char[] {separator}); + } + if (hasChar() && (chars[pos] == separator)) { + pos++; // skip separator + } + if ((paramName != null) && (paramName.length() > 0)) { + params.add(new NameValuePair(paramName, paramValue)); + } + } + return params; + } +} Index: test/org/apache/commons/httpclient/TestHeaderElement.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHeaderElement.java,v retrieving revision 1.5 diff -u -r1.5 TestHeaderElement.java --- test/org/apache/commons/httpclient/TestHeaderElement.java 28 Jan 2003 04:40:23 -0000 1.5 +++ test/org/apache/commons/httpclient/TestHeaderElement.java 12 Jul 2003 14:36:02 -0000 @@ -70,6 +70,7 @@ * @author Rodney Waldhoff * @author B.C. Holmes * @author Park, Sung-Gu + * @author oleg Kalnichevski * @version $Id: TestHeaderElement.java,v 1.5 2003/01/28 04:40:23 jsdever Exp $ */ public class TestHeaderElement extends TestNVP { @@ -108,7 +109,7 @@ // this is derived from the old main method in HeaderElement String headerValue = "name1 = value1; name2; name3=\"value3\" , name4=value4; " + "name5=value5, name6= ; name7 = value7; name8 = \" value8\""; - HeaderElement[] elements = HeaderElement.parse(headerValue); + HeaderElement[] elements = HeaderElement.parseElements(headerValue); // there are 3 elements assertEquals(3,elements.length); // 1st element @@ -129,12 +130,32 @@ assertEquals("value5",elements[1].getParameters()[0].getValue()); // 3rd element assertEquals("name6",elements[2].getName()); - assertTrue(null == elements[2].getValue()); + assertEquals(null,elements[2].getValue()); // 3rd element has 2 getParameters() assertEquals(2,elements[2].getParameters().length); assertEquals("name7",elements[2].getParameters()[0].getName()); assertEquals("value7",elements[2].getParameters()[0].getValue()); assertEquals("name8",elements[2].getParameters()[1].getName()); assertEquals(" value8",elements[2].getParameters()[1].getValue()); + } + + public void testFringeCase1() throws Exception { + String headerValue = "name1 = value1,"; + HeaderElement[] elements = HeaderElement.parseElements(headerValue); + assertEquals("Number of elements", 1, elements.length); + } + + + public void testFringeCase2() throws Exception { + String headerValue = "name1 = value1, "; + HeaderElement[] elements = HeaderElement.parseElements(headerValue); + assertEquals("Number of elements", 1, elements.length); + } + + + public void testFringeCase3() throws Exception { + String headerValue = ",, ,, ,"; + HeaderElement[] elements = HeaderElement.parseElements(headerValue); + assertEquals("Number of elements", 0, elements.length); } } Index: test/org/apache/commons/httpclient/TestNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v retrieving revision 1.22 diff -u -r1.22 TestNoHost.java --- test/org/apache/commons/httpclient/TestNoHost.java 17 Apr 2003 11:34:19 -0000 1.22 +++ test/org/apache/commons/httpclient/TestNoHost.java 12 Jul 2003 14:36:02 -0000 @@ -87,6 +87,7 @@ suite.addTest(TestCookie.suite()); suite.addTest(TestNVP.suite()); suite.addTest(TestHeader.suite()); + suite.addTest(TestParameterParser.suite()); suite.addTest(TestHeaderElement.suite()); suite.addTest(TestChallengeParser.suite()); suite.addTest(TestAuthenticator.suite()); Index: test/org/apache/commons/httpclient/TestParameterParser.java =================================================================== RCS file: test/org/apache/commons/httpclient/TestParameterParser.java diff -N test/org/apache/commons/httpclient/TestParameterParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/org/apache/commons/httpclient/TestParameterParser.java 12 Jul 2003 14:36:03 -0000 @@ -0,0 +1,133 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.util.List; + +import org.apache.commons.httpclient.util.ParameterParser; + +/** + * Unit tests for {@link ParameterParser}. + * + * @author Oleg Kalnichevski + */ +public class TestParameterParser extends TestCase { + + // ------------------------------------------------------------ Constructor + public TestParameterParser(String testName) { + super(testName); + } + + // ------------------------------------------------------------------- Main + public static void main(String args[]) { + String[] testCaseName = { TestParameterParser.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + // ------------------------------------------------------- TestCase Methods + + public static Test suite() { + return new TestSuite(TestParameterParser.class); + } + + + public void testParsing() { + String s = + "test; test1 = stuff ; test2 = \"stuff; stuff\"; test3=\"stuff"; + ParameterParser parser = new ParameterParser(); + List params = parser.parse(s, ';'); + assertEquals("test", ((NameValuePair)params.get(0)).getName()); + assertEquals(null, ((NameValuePair)params.get(0)).getValue()); + assertEquals("test1", ((NameValuePair)params.get(1)).getName()); + assertEquals("stuff", ((NameValuePair)params.get(1)).getValue()); + assertEquals("test2", ((NameValuePair)params.get(2)).getName()); + assertEquals("stuff; stuff", ((NameValuePair)params.get(2)).getValue()); + assertEquals("test3", ((NameValuePair)params.get(3)).getName()); + assertEquals("\"stuff", ((NameValuePair)params.get(3)).getValue()); + + s = " test , test1=stuff , , test2=, test3, "; + params = parser.parse(s, ','); + assertEquals("test", ((NameValuePair)params.get(0)).getName()); + assertEquals(null, ((NameValuePair)params.get(0)).getValue()); + assertEquals("test1", ((NameValuePair)params.get(1)).getName()); + assertEquals("stuff", ((NameValuePair)params.get(1)).getValue()); + assertEquals("test2", ((NameValuePair)params.get(2)).getName()); + assertEquals(null, ((NameValuePair)params.get(2)).getValue()); + assertEquals("test3", ((NameValuePair)params.get(3)).getName()); + assertEquals(null, ((NameValuePair)params.get(3)).getValue()); + + s = " test"; + params = parser.parse(s, ';'); + assertEquals("test", ((NameValuePair)params.get(0)).getName()); + assertEquals(null, ((NameValuePair)params.get(0)).getValue()); + + s = " "; + params = parser.parse(s, ';'); + assertEquals(0, params.size()); + + s = " = stuff "; + params = parser.parse(s, ';'); + assertEquals(0, params.size()); + } +}