Index: src/test/org/apache/commons/httpclient/TestNoHost.java
===================================================================
--- src/test/org/apache/commons/httpclient/TestNoHost.java (revision 171105)
+++ src/test/org/apache/commons/httpclient/TestNoHost.java (working copy)
@@ -64,6 +64,7 @@
suite.addTest(TestRequestHeaders.suite());
suite.addTest(TestStreams.suite());
suite.addTest(TestParameterParser.suite());
+ suite.addTest(TestParameterFormatter.suite());
suite.addTest(TestNVP.suite());
suite.addTest(TestMethodCharEncoding.suite());
suite.addTest(TestHttpVersion.suite());
Index: src/test/org/apache/commons/httpclient/TestParameterParser.java
===================================================================
--- src/test/org/apache/commons/httpclient/TestParameterParser.java (revision 171105)
+++ src/test/org/apache/commons/httpclient/TestParameterParser.java (working copy)
@@ -1,4 +1,7 @@
/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
* ====================================================================
*
* Copyright 1999-2004 The Apache Software Foundation
@@ -21,8 +24,6 @@
* information on the Apache Software Foundation, please see
*
RFC 2109 specific cookie management functions
@@ -51,12 +52,15 @@
public class RFC2109Spec extends CookieSpecBase {
+ private final ParameterFormatter formatter;
+
/** Default constructor */
public RFC2109Spec() {
super();
+ this.formatter = new ParameterFormatter();
+ this.formatter.setAlwaysUseQuotes(true);
}
-
/**
* Parse RFC 2109 specific cookie attribute and update the corresponsing
* {@link Cookie} properties.
@@ -189,60 +193,46 @@
* Return a name/value string suitable for sending in a "Cookie"
* header as defined in RFC 2109 for backward compatibility with cookie
* version 0
- * @param name The name.
- * @param value The value
+ * @param buffer The string buffer to use for output
+ * @param param The parameter.
* @param version The cookie version
- * @return a string suitable for sending in a "Cookie" header.
*/
-
- private String formatNameValuePair(
- final String name, final String value, int version) {
-
- final StringBuffer buffer = new StringBuffer();
+ private void formatParam(final StringBuffer buffer, final NameValuePair param, int version) {
if (version < 1) {
- buffer.append(name);
+ buffer.append(param.getName());
buffer.append("=");
- if (value != null) {
- buffer.append(value);
+ if (param.getValue() != null) {
+ buffer.append(param.getValue());
}
} else {
- buffer.append(name);
- buffer.append("=\"");
- if (value != null) {
- buffer.append(value);
- }
- buffer.append("\"");
+ this.formatter.format(buffer, param);
}
- return buffer.toString();
}
/**
* Return a string suitable for sending in a "Cookie" header
* as defined in RFC 2109 for backward compatibility with cookie version 0
- * @param cookie a {@link Cookie} to be formatted as string
+ * @param buffer The string buffer to use for output
+ * @param cookie The {@link Cookie} to be formatted as string
* @param version The version to use.
- * @return a string suitable for sending in a "Cookie" header.
*/
- private String formatCookieAsVer(Cookie cookie, int version) {
- LOG.trace("enter RFC2109Spec.formatCookieAsVer(Cookie)");
- StringBuffer buf = new StringBuffer();
- buf.append(formatNameValuePair(cookie.getName(),
- cookie.getValue(), version));
+ private void formatCookieAsVer(final StringBuffer buffer, final Cookie cookie, int version) {
+ String value = cookie.getValue();
+ if (value == null) {
+ value = "";
+ }
+ formatParam(buffer, new NameValuePair(cookie.getName(), value), version);
if (cookie.getDomain() != null
&& cookie.isDomainAttributeSpecified()) {
-
- buf.append("; ");
- buf.append(formatNameValuePair("$Domain",
- cookie.getDomain(), version));
+ buffer.append("; ");
+ formatParam(buffer, new NameValuePair("$Domain", cookie.getDomain()), version);
}
if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
- buf.append("; ");
- buf.append(formatNameValuePair("$Path", cookie.getPath(), version));
+ buffer.append("; ");
+ formatParam(buffer, new NameValuePair("$Path", cookie.getPath()), version);
}
- return buf.toString();
}
-
/**
* Return a string suitable for sending in a "Cookie" header as
* defined in RFC 2109
@@ -254,12 +244,13 @@
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
- int ver = cookie.getVersion();
+ int version = cookie.getVersion();
StringBuffer buffer = new StringBuffer();
- buffer.append(formatNameValuePair("$Version",
- Integer.toString(ver), ver));
+ formatParam(buffer,
+ new NameValuePair("$Version", Integer.toString(version)),
+ version);
buffer.append("; ");
- buffer.append(formatCookieAsVer(cookie, ver));
+ formatCookieAsVer(buffer, cookie, version);
return buffer.toString();
}
@@ -281,11 +272,12 @@
}
}
final StringBuffer buffer = new StringBuffer();
- buffer.append(formatNameValuePair("$Version",
- Integer.toString(version), version));
+ formatParam(buffer,
+ new NameValuePair("$Version", Integer.toString(version)),
+ version);
for (int i = 0; i < cookies.length; i++) {
buffer.append("; ");
- buffer.append(formatCookieAsVer(cookies[i], version));
+ formatCookieAsVer(buffer, cookies[i], version);
}
return buffer.toString();
}
Index: src/java/org/apache/commons/httpclient/util/ParameterParser.java
===================================================================
--- src/java/org/apache/commons/httpclient/util/ParameterParser.java (revision 171105)
+++ src/java/org/apache/commons/httpclient/util/ParameterParser.java (working copy)
@@ -142,16 +142,19 @@
i1 = pos;
i2 = pos;
boolean quoted = false;
+ boolean charEscaped = false;
while (hasChar()) {
ch = chars[pos];
if (!quoted && isOneOf(ch, terminators)) {
break;
}
- if (ch == '"') {
+ if (!charEscaped && ch == '"') {
quoted = !quoted;
}
+ charEscaped = (!charEscaped && ch == '\\');
i2++;
pos++;
+
}
return getToken(true);
}
Index: src/java/org/apache/commons/httpclient/util/ParameterFormatter.java
===================================================================
--- /dev/null 2005-05-25 17:54:26.487000000 +0200
+++ src/java/org/apache/commons/httpclient/util/ParameterFormatter.java 2005-05-25 17:50:42.290487400 +0200
@@ -0,0 +1,241 @@
+/*
+ * $HeadURL: $
+ * $Revision: $
+ * $Date: $
+ *
+ * ====================================================================
+ *
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * 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
+ *
+ * This formatter produces a textual representation of attribute/value pairs. It + * comforms to the generic grammar and formatting rules outlined in the + * Section 2.1 + * and + * Section 3.6 + * of RFC 2616 + *
+ *+ * Many HTTP/1.1 header field values consist of words separated by LWS or special + * characters. These special characters MUST be in a quoted string to be used within + * a parameter value (as defined in section 3.6). + *
+ *
+ * token = 1*+ *+ * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + *
+ * A string of text is parsed as a single word if it is quoted using double-quote marks. + *
+ *+ * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext =+ *> + *
+ * The backslash character ("\") MAY be used as a single-character quoting mechanism only + * within quoted-string and comment constructs. + *
+ *+ * quoted-pair = "\" CHAR + *+ *
+ * Parameters are in the form of attribute/value pairs. + *
+ *+ * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + *+ * + * @author Oleg Kalnichevski + * + * @since 3.0 + */ +public class ParameterFormatter { + + /** + * Special characters that can be used as separators in HTTP parameters. + * These special characters MUST be in a quoted string to be used within + * a parameter value + */ + private static final char[] SEPARATORS = { + '(', ')', '<', '>', '@', + ',', ';', ':', '\\', '"', + '/', '[', ']', '?', '=', + '{', '}', ' ', '\t' + }; + + /** + * Unsafe special characters that must be escaped using the backslash + * character + */ + private static final char[] UNSAFE_CHARS = { + '"', '\\' + }; + + /** + * This flag determines whether all parameter values must be enclosed in + * quotation marks, even if they do not contain any special characters + */ + private boolean alwaysUseQuotes = true; + + /** Default ParameterFormatter constructor */ + public ParameterFormatter() { + super(); + } + + private static boolean isOneOf(char[] chars, char ch) { + for (int i = 0; i < chars.length; i++) { + if (ch == chars[i]) { + return true; + } + } + return false; + } + + private static boolean isUnsafeChar(char ch) { + return isOneOf(UNSAFE_CHARS, ch); + } + + private static boolean isSeparator(char ch) { + return isOneOf(SEPARATORS, ch); + } + + /** + * Determines whether all parameter values must be enclosed in quotation + * marks, even if they do not contain any special characters + * + * @return true if all parameter values must be enclosed in + * quotation marks, false otherwise + */ + public boolean isAlwaysUseQuotes() { + return alwaysUseQuotes; + } + + /** + * Defines whether all parameter values must be enclosed in quotation + * marks, even if they do not contain any special characters + * + * @param alwaysUseQuotes + */ + public void setAlwaysUseQuotes(boolean alwaysUseQuotes) { + this.alwaysUseQuotes = alwaysUseQuotes; + } + + /** + * Formats the given parameter value using formatting rules defined + * in RFC 2616 + * + * @param buffer output buffer + * @param value the parameter value to be formatted + * @param alwaysUseQuotes true if the parameter value must + * be enclosed in quotation marks, even if it does not contain any special + * characters, false only if the parameter value contains + * potentially unsafe special characters + */ + public static void formatValue( + final StringBuffer buffer, final String value, boolean alwaysUseQuotes) { + if (buffer == null) { + throw new IllegalArgumentException("String buffer may not be null"); + } + if (value == null) { + throw new IllegalArgumentException("Value buffer may not be null"); + } + if (alwaysUseQuotes) { + buffer.append('"'); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (isUnsafeChar(ch)) { + buffer.append('\\'); + } + buffer.append(ch); + } + buffer.append('"'); + } else { + int offset = buffer.length(); + boolean unsafe = false; + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (isSeparator(ch)) { + unsafe = true; + } + if (isUnsafeChar(ch)) { + buffer.append('\\'); + } + buffer.append(ch); + } + if (unsafe) { + buffer.insert(offset, '"'); + buffer.append('"'); + } + } + } + + /** + * Produces textual representaion of the attribute/value pair using + * formatting rules defined in RFC 2616 + * + * @param buffer output buffer + * @param param the parameter to be formatted + */ + public void format(final StringBuffer buffer, final NameValuePair param) { + if (buffer == null) { + throw new IllegalArgumentException("String buffer may not be null"); + } + if (param == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + buffer.append(param.getName()); + String value = param.getValue(); + if (value != null) { + buffer.append("="); + formatValue(buffer, value, this.alwaysUseQuotes); + } + } + + /** + * Produces textual representaion of the attribute/value pair using + * formatting rules defined in RFC 2616 + * + * @param param the parameter to be formatted + * + * @return RFC 2616 conformant textual representaion of the + * attribute/value pair + */ + public String format(final NameValuePair param) { + StringBuffer buffer = new StringBuffer(); + format(buffer, param); + return buffer.toString(); + } + +} Index: src/test/org/apache/commons/httpclient/TestParameterFormatter.java =================================================================== --- /dev/null 2005-05-25 17:57:13.025000000 +0200 +++ src/test/org/apache/commons/httpclient/TestParameterFormatter.java 2005-05-25 17:56:41.061868500 +0200 @@ -0,0 +1,88 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * ==================================================================== + * + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * 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 + *