You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by js...@apache.org on 2002/12/08 07:09:47 UTC
cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestCookie.java TestWebappCookie.java
jsdever 2002/12/07 22:09:46
Modified: httpclient/src/java/org/apache/commons/httpclient
Cookie.java
httpclient/src/test/org/apache/commons/httpclient
TestCookie.java TestWebappCookie.java
Added: httpclient/src/java/org/apache/commons/httpclient/cookie
CookiePolicy.java CookieSpec.java
CookieSpecBase.java HttpMethodBase.java
MalformedCookieException.java
NetscapeDraftSpec.java RFC2109Spec.java
Log:
Cookie refactoring into a new cookie package.
Changes:
- Policy driven cookie management
- Improved Netscape draft compliance
- Full RFC2109 compliance
- Compatibility mode to simulate relaxed cookie management of popular
web browsers
- In strict mode all the cookies are still crammed into one Cookie
header. In non-strict mode, however, each cookie is sent in a separate
header
Bug fixes:
- Cookie constructor now rejects cookie name containing blanks
- Cookie constructor now not rejects cookie name starting with $ (That's
a bad one)
- Netscape compliant spec now properly formats Cookie header value
(domain and path attributes not included)
- RFC2109 compliant spec now properly formats Cookie header value
(cookie value and attribute values are quoted)
Contributed by: Oleg Kalnichevski
Revision Changes Path
1.29 +35 -460 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Cookie.java
Index: Cookie.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Cookie.java,v
retrieving revision 1.28
retrieving revision 1.29
diff -u -r1.28 -r1.29
--- Cookie.java 2 Dec 2002 06:14:56 -0000 1.28
+++ Cookie.java 8 Dec 2002 06:09:46 -0000 1.29
@@ -66,18 +66,14 @@
import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
-import java.text.DateFormat;
-import java.text.ParseException;
import java.text.RuleBasedCollator;
-import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import java.util.StringTokenizer;
-
+import org.apache.commons.httpclient.cookie.*;
/**
@@ -141,6 +137,12 @@
if (name.equals("")) {
throw new IllegalArgumentException("Cookie name may not be blank");
}
+ if (name.indexOf(' ') != -1) {
+ throw new IllegalArgumentException("Cookie name may contain blanks");
+ }
+ if (name.startsWith("$")) {
+ throw new IllegalArgumentException("Cookie name may start with $");
+ }
this.setPath(path);
this.setDomain(domain);
this.setExpiryDate(expires);
@@ -443,17 +445,7 @@
* @return a string suitable for sending in a Cookie header.
*/
public String toExternalForm() {
- StringBuffer buf = new StringBuffer();
- buf.append(getName()).append("=").append(getValue());
- if (_path != null && isPathAttributeSpecified()) {
- buf.append("; $Path=");
- buf.append(_path);
- }
- if (_domain != null && isDomainAttributeSpecified()) {
- buf.append("; $Domain=");
- buf.append(_domain);
- }
- return buf.toString();
+ return CookiePolicy.getSpecByVersion(getVersion()).formatCookie(this);
}
/**
@@ -465,49 +457,11 @@
* @param secure <tt>true</tt> if the request is using the HTTPS protocol
* @param date the time at which the request is submitted
*/
- public boolean matches(String domain, int port, String path, boolean secure, Date date) {
+ public boolean matches(String domain, int port, String path, boolean secure, Date date)
+ {
log.trace("enter Cookie.matches(Strinng, int, String, boolean, Date");
- // FIXME: RFC2109 doesn't consider ports when filtering/matching
- // cookies. Quoting from Section 2 - Terminology:
- // The terms request-host and request-URI refer to
- // the values the client would send to the server
- // as, respectively, the host (but not port) and
- // abs_path portions of the absoluteURI (http_URL)
- // of the HTTP request line.
- //
- // RFC2965 includes ports in cookie-sending
- // determination, but only when the cookie is received
- // via a 'Set-Cookie2' header.
- //
- // The current implementation doesn't support RFC2965,
- // and ignores ports when matching cookies.
- if (domain == null) {
- throw new IllegalArgumentException("domain parameter may not be null");
- }
- if (path == null) {
- throw new IllegalArgumentException("path parameter may not be null");
- }
- if (date == null) {
- throw new IllegalArgumentException("date parameter may not be null");
- }
- if (getDomain() == null) {
- log.warn("Invalid cookie state: domain not specified");
- return false;
- }
- if (getPath() == null) {
- log.warn("Invalid cookie state: path not specified");
- return false;
- }
-
- domain = domain.toLowerCase();
-
- return (
- (getExpiryDate() == null ||
- getExpiryDate().after(date)) && // only add the cookie if it hasn't yet expired
- (domainMatch(domain, getDomain())) && // and the domain pattern matches
- (pathMatch(path, getPath())) && // and the path is null or matching
- (getSecure() ? secure : true) // and if the secure flag is set, only if the request is actually secure
- );
+ CookieSpec matcher = CookiePolicy.getDefaultSpec();
+ return matcher.match(domain, port, path, secure, this);
}
/**
@@ -593,49 +547,20 @@
* If no cookies match, returns null.
* @exception java.lang.IllegalArgumentException if domain or path is null
*/
+
public static Header createCookieHeader(String domain, int port, String path, boolean secure, Date now, Cookie[] cookies)
throws IllegalArgumentException {
- log.trace("enter Cookie.createCookieHeader(String, int, String, boolean, Date, Cookie[])");
-
- // Validate the domain and path
- if(domain == null){
- throw new IllegalArgumentException("null domain in createCookieHeader.");
- }
- if(path == null){
- throw new IllegalArgumentException("null path in createCookieHeader.");
- }
-
- boolean added = false;
- StringBuffer value = new StringBuffer();
-
- if (cookies.length <= 0) {
- return null;
- }
- List addedCookies = new LinkedList();
- for(int i=0;i<cookies.length;i++) {
- if(cookies[i].matches(domain,port,path,secure,now)) {
- addInPathOrder(addedCookies, cookies[i]);
- added = true;
- }
+ log.trace("enter Cookie.createCookieHeader(String, int, String, boolean, Date, Cookie[])");
+ CookieSpec matcher = CookiePolicy.getDefaultSpec();
+ cookies = matcher.match(domain, port, path, secure, cookies);
+ if ((cookies != null) && (cookies.length > 0))
+ {
+ return matcher.formatCookieHeader(cookies);
}
- if (added) {
- /* FIXME. We assume here that all the cookies we're sending back
- * are the same version. A better solution might be to separate
- * cookies of different versions and send them on separate cookie
- * headers.
- */
- value.append("$Version=");
- value.append(((Cookie)addedCookies.get(0)).getVersion());
- for (Iterator itr = addedCookies.iterator(); itr.hasNext(); ) {
- Cookie cookie = (Cookie)itr.next();
- value.append("; ");
- value.append(cookie.toExternalForm());
- }
-
- return new Header("Cookie", value.toString());
- } else {
+ else
+ {
return null;
- }
+ }
}
/**
@@ -662,14 +587,14 @@
return 0;
} else if (c1.getPath() == null) {
// null is assumed to be "/"
- if (c2.getPath().equals(PATH_DELIM)) {
+ if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
return 0;
} else {
return -1;
}
} else if (c2.getPath() == null) {
// null is assumed to be "/"
- if (c1.getPath().equals(PATH_DELIM)) {
+ if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
return 0;
} else {
return 1;
@@ -774,349 +699,18 @@
throws HttpException, IllegalArgumentException {
log.trace("enter Cookie.parse(String, int, String, boolean, Header)");
- // Validate domain and path
- if(domain == null){
- throw new IllegalArgumentException("domain may not be null.");
- }
- if(path == null){
- throw new IllegalArgumentException("path may not be null.");
- }
- if(setCookie == null){
- throw new IllegalArgumentException("set-cookie header may not be null.");
- }
-
- // Normalize domain name
- domain = domain.toLowerCase();
-
- /* Build the default path. Per RFC 2109/4.3.1 this is the
- * request path up to, but not including, the right-most / charater.
- */
- if (path.length() == 0) {
- log.debug("Fixing up empty request path.");
- path = PATH_DELIM;
- }
- String defaultPath = null;
- int lastSlashIndex = path.lastIndexOf(PATH_DELIM);
- if(lastSlashIndex == 0){
- defaultPath = PATH_DELIM;
- }
- else if(lastSlashIndex > 0) {
- defaultPath = path.substring(0, lastSlashIndex);
- }
- else {
- defaultPath = path;
- }
-
- HeaderElement[] headerElements =
- HeaderElement.parse(setCookie.getValue());
-
- Cookie[] cookies = new Cookie[headerElements.length];
-
- int index = 0;
- for (int i = 0; i < headerElements.length; i++) {
-
- HeaderElement headerelement = headerElements[i];
- Cookie cookie = new Cookie(domain,
- headerelement.getName(),
- headerelement.getValue(),
- defaultPath,
- null,
- false);
-
- // cycle through the parameters
- NameValuePair[] parameters = headerelement.getParameters();
- // could be null. In case only a header element and no parameters.
- if (parameters != null) {
-
- for (int j = 0; j < parameters.length; j++) {
-
- String param_name = parameters[j].getName().toLowerCase();
- String param_value = parameters[j].getValue();
-
- if (param_name.equals("version")) {
-
- if (param_value == null) {
- throw new HttpException("Missing value for version attribute");
- }
- try {
- cookie.setVersion(Integer.parseInt(param_value));
- } catch (NumberFormatException e) {
- throw new HttpException( "Invalid version attribute: " + e.getMessage());
- }
-
- }
- else if (param_name.equals("path")) {
-
- if (param_value == null) {
- throw new HttpException("Missing value for path attribute");
- }
- cookie.setPath(param_value);
- cookie.setPathAttributeSpecified(true);
-
- }
- else if (param_name.equals("domain")) {
-
- if (param_value == null) {
- throw new HttpException("Missing value for domain attribute");
- }
- cookie.setDomain(param_value);
- cookie.setDomainAttributeSpecified(true);
-
- }
- else if (param_name.equals("max-age")) {
-
- if (param_value == null) {
- throw new HttpException("Missing value for max-age attribute");
- }
- int age;
- try {
- age = Integer.parseInt(param_value);
- } catch (NumberFormatException e) {
- throw new HttpException( "Invalid max-age attribute: " + e.getMessage());
- }
- cookie.setExpiryDate(new Date(System.currentTimeMillis() +
- age * 1000L));
-
- }
- else if (param_name.equals("secure")) {
-
- cookie.setSecure(true);
-
- }
- else if (param_name.equals("comment")) {
-
- cookie.setComment(param_value);
-
- }
- else if (param_name.equals("expires")) {
-
- if (param_value == null) {
- throw new HttpException("Missing value for expires attribute");
- }
- boolean set = false;
- // trim single quotes around expiry if present
- // see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279
- if(param_value.length() > 1 &&
- param_value.startsWith("'") &&
- param_value.endsWith("'")) {
- param_value = param_value.substring(1,param_value.length()-1);
- }
-
- for(int k=0;k<expiryFormats.length;k++) {
-
- try {
- Date date = expiryFormats[k].parse(param_value);
- cookie.setExpiryDate(date);
- set = true;
- break;
- } catch (ParseException e) {
- //Ignore and move on
- }
- }
- if(!set) {
- throw new HttpException("Unable to parse expiration date parameter: " + param_value);
- }
- }
- }
- }
-
- // Check if the cookie has all required attributes. If not, apply defaults
- if (cookie.getDomain() == null) {
- cookie.setDomain(domain);
- cookie.setDomainAttributeSpecified(false);
- if (log.isDebugEnabled()) {
- log.debug("Domain attribute not explicitly set. Default applied: \"" + domain + "\"");
- }
- }
- if (cookie.getPath() == null) {
- cookie.setPath(defaultPath);
- cookie.setPathAttributeSpecified(false);
- if (log.isDebugEnabled()) {
- log.debug("Path attribute not explicitly set. Default applied: \"" + defaultPath + "\"");
- }
- }
+ CookieSpec parser = CookiePolicy.getDefaultSpec();
+ Cookie[] cookies = parser.parse(domain, port, path, secure, setCookie);
- validate(cookie, domain, port, path, secure);
-
- if(log.isDebugEnabled()){
- log.debug("Cookie accepted: \"" + cookie.toString() +"\"");
- }
- cookies[index++] = cookie;
+ for (int i = 0; i < cookies.length; i++)
+ {
+ Cookie cookie = cookies[i];
+ CookieSpec validator = CookiePolicy.getSpecByVersion(cookie.getVersion());
+ validator.validate(domain, port, path, secure, cookie);
}
return cookies;
}
-
- private static void validate(Cookie cookie, String domain, int port, String path, boolean secure)
- throws HttpException {
- log.trace("enter Cookie.validate(String, int, String, boolean)");
- // This private method does not validate any input parameters under assumption
- // that parameters are properly invalidated by the caller
-
- // check version
- if (cookie.getVersion() < 0) {
- throw new HttpException( "Illegal version number " + cookie.getValue());
- }
-
- // security check... we musn't allow the server to give us an
- // invalid domain scope
-
- // Validate the cookies domain attribute. NOTE: Domains without any dots are
- // allowed to support hosts on private LANs that don't have DNS names. Since
- // they have no dots, to domain-match the request-host and domain must be identical
- // for the cookie to sent back to the origin-server.
- if (!cookie.getDomain().equals("localhost") && domain.indexOf(".") >= 0) {
-
- // Not required to have at least two dots. RFC 2965.
- // A Set-Cookie2 with Domain=ajax.com will be accepted.
-
- // domain must domain match host
- if (!domain.endsWith(cookie.getDomain())){
-
- throw new HttpException(
- "Illegal domain attribute \"" + cookie.getDomain() + "\". Domain of origin: \"" + domain + "\"");
-
- }
-
- switch(cookie.getVersion()) {
- case 0:
- // Validate domain using Netscape cookie specification
- validateDomainAttribVer0(cookie, domain);
- break;
- default:
- validateDomainAttribVer1(cookie, domain);
- break;
- }
- }
-
- // another security check... we musn't allow the server to give us a
- // cookie that doesn't match this path
-
- if(!path.startsWith(cookie.getPath())) {
-
- throw new HttpException(
- "Illegal path attribute \"" + cookie.getPath() + "\". Path of origin: \"" + path + "\"");
- }
- }
-
- /**
- * Validate domain attribute using Netscape cookie specification draft
- */
-
- private static void validateDomainAttribVer0(final Cookie cookie, final String domain)
- throws HttpException {
- log.trace("enter Cookie.validateDomainAttribVer0()");
-
- int domainParts = new StringTokenizer(cookie.getDomain(), ".").countTokens();
-
- if (isSpecialDomain(cookie.getDomain())) {
-
- if(domainParts < 2) {
-
- throw new HttpException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification for special domains");
- }
- }
- else {
-
- if(domainParts < 3) {
-
- throw new HttpException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification");
- }
- }
- }
-
- /**
- * Validate domain using RFC 2109
- */
-
- private static void validateDomainAttribVer1(final Cookie cookie, final String domain)
- throws HttpException {
- log.trace("enter Cookie.validateDomainAttribVer1()");
-
- // domain must have at least one embedded dot
- int dotIndex = cookie.getDomain().indexOf('.', 1);
- if(dotIndex < 0 || dotIndex == cookie.getDomain().length()-1){
-
- throw new HttpException("Illegal domain attribute \"" + cookie.getDomain() + "\"");
-
- }
- // host minus domain may not contain any dots
- if (domain.substring(0,
- domain.length() -
- cookie.getDomain().length()).indexOf('.') != -1) {
-
- throw new HttpException("Illegal domain attribute \"" + cookie.getDomain() + "\"");
- }
- }
-
- /**
- * Checks if the given domain is in one of the seven special
- * top level domains defined by the Netscape cookie specification.
- */
- private static boolean isSpecialDomain(String domain)
- {
- String ucDomain = domain.toUpperCase();
- if(ucDomain.endsWith(".COM") ||
- ucDomain.endsWith(".EDU") ||
- ucDomain.endsWith(".NET") ||
- ucDomain.endsWith(".GOV") ||
- ucDomain.endsWith(".MIL") ||
- ucDomain.endsWith(".ORG") ||
- ucDomain.endsWith(".INT")){
- return true;
- }
-
- return false;
-
- }
-
- /**
- * Performs a domain-match as described in RFC2109.
- */
- private static boolean domainMatch(String host, String domain)
- {
- boolean match = host.equals(domain) ||
- (domain.startsWith(".") && host.endsWith(domain));
-
- return match;
- }
-
- /**
- * Performs a path-match slightly smarter than a straight-forward startsWith check.
- */
- private static boolean pathMatch(final String path, final String topmostPath)
- {
- boolean match = path.startsWith( topmostPath );
- if (match) {
- if (!topmostPath.endsWith(PATH_DELIM)) {
- match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
- }
- }
- return match;
- }
-
- /**
- * Adds the given cookie into the given list in descending path order. That is,
- * more specific path to least specific paths. This may not be the fastest
- * algorythm, but it'll work OK for the small number of cookies we're
- * generally dealing with.
- *
- * @param list - the list to add the cookie to
- * @param addCookie - the Cookie to add to list
- */
- private static void addInPathOrder(List list, Cookie addCookie)
- {
- int i = 0;
-
- for(i=0;i<list.size();i++){
- Cookie c = (Cookie)list.get(i);
- if(addCookie.compare(addCookie, c) > 0){
- break;
- }
- }
- list.add(i, addCookie);
- }
-
// ----------------------------------------------------- Instance Variables
/** My comment. */
@@ -1145,29 +739,10 @@
// -------------------------------------------------------------- Constants
- /** List of valid date formats for the "expires" cookie attribute. */
- private static final DateFormat[] expiryFormats = {
- // RFC 1123, 822, Date and time specification is English.
- new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE dd-MMM-yy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE dd-MMM-yyyy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE dd MMM yy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US),
- new SimpleDateFormat("EEE, dd-MMM-yyyy HH-mm-ss z", Locale.US),
- new SimpleDateFormat("EEE dd-MMM-yyyy HH-mm-ss z", Locale.US)
- };
-
/** Collator for Cookie comparisons. Could be replaced with references to specific Locales. */
private static final RuleBasedCollator stringCollator =
(RuleBasedCollator)RuleBasedCollator.getInstance(
new Locale("en", "US", ""));
-
- /** Path delimiter */
- private static final String PATH_DELIM = "/";
- private static final char PATH_DELIM_CHAR = PATH_DELIM.charAt(0);
/** Log object for this class */
private static final Log log = LogFactory.getLog(Cookie.class);
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java
Index: CookiePolicy.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
/**
* <p>
* Cookie management policy class. The cookie policy provides corresponding cookie
* management interfrace for a given type or version of cookie.
* </p>
* <p>
* RFC 2109 specification is used per default. Other supported specification can be
* chosen when appropriate or set default when desired
* </p>
* <p>
* The following specifications are provided:
* <tt>COMPATIBILITY</tt>: compatible with the common cookie management practices
* (even if they are not 100% standards compliant
* <tt>NETSCAPE_DRAFT</tt>: Netscape cookie draft compliant
* <tt>RFC2109</tt>: RFC2109 compliant (default)
* </p>
* <p>
* Default policy can be set on JVM start-up through the system property
* "apache.commons.httpclient.cookiespec". Recognized values:
* COMPATIBILITY, NETSCAPE_DRAFT, RFC2109.
* </p>
*
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
public abstract class CookiePolicy
{
private static final String SYSTEM_PROPERTY = "apache.commons.httpclient.cookiespec";
/**
* The <tt>COMPATIBILITY</tt> policy provides high compatibilty
* with common cookie management of popular HTTP agents
*/
public static final int COMPATIBILITY = 0;
/**
* The <tt>NETSCAPE_DRAFT</tt> Netscape draft compliant policy
*/
public static final int NETSCAPE_DRAFT = 1;
/**
* The <tt>RFC2109</tt> RFC 2109 compliant policy (default)
*/
public static final int RFC2109 = 2;
private static int DefaultPolicy = RFC2109;
static {
String s = System.getProperty(SYSTEM_PROPERTY);
if (s != null)
{
if (s.equalsIgnoreCase("COMPATIBILITY"))
{
setDefaultPolicy(COMPATIBILITY);
}else
if (s.equalsIgnoreCase("NETSCAPE_DRAFT"))
{
setDefaultPolicy(NETSCAPE_DRAFT);
}else
if (s.equalsIgnoreCase("RFC2109"));
{
setDefaultPolicy(RFC2109);
}
}
}
/**
* @return default cookie policy (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
*/
public static int getDefaultPolicy()
{
return DefaultPolicy;
}
/**
* @param new default cookie policy (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
*/
public static void setDefaultPolicy(int policy)
{
DefaultPolicy = policy;
}
/**
* @return cookie specification interface for the given policy
* (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
*/
public static CookieSpec getSpecByPolicy(int policy)
{
switch(policy)
{
case COMPATIBILITY:
return new CookieSpecBase();
case NETSCAPE_DRAFT:
return new NetscapeDraftSpec();
case RFC2109:
return new RFC2109Spec();
default:
return new RFC2109Spec();
}
}
/**
* @return default cookie specification interface
*/
public static CookieSpec getDefaultSpec()
{
return getSpecByPolicy(DefaultPolicy);
}
/**
* @return cookie specification interface intended for processing
* cookies with the given version
*
* Supported versions:
* <tt>version 0</tt> corresponds to the NETSCAPE_DRAFT
* <tt>version 1</tt> corresponds to the RFC2109
* Any unsupported cookie version number corresponds to
* the current default cookie specification
*/
public static CookieSpec getSpecByVersion(int ver)
{
switch(ver)
{
case 0:
return new NetscapeDraftSpec();
case 1:
return new RFC2109Spec();
default:
return getDefaultSpec();
}
}
/**
* @return cookie specification interface that provides high compatibilty
* with common cookie management of popular HTTP agents
*/
public static CookieSpec getCompatibilitySpec()
{
return getSpecByPolicy(COMPATIBILITY);
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java
Index: CookieSpec.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.Cookie;
/**
* <p>
* Cookie management specification must define
* - rules of parsing "Set-Cookie" header
* - rules of validation of parsed cookies
* - formatting of "Cookie" header
* for a given host, port and path of origin
* </p>
*
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
public interface CookieSpec
{
/** Path delimiter */
public static final String PATH_DELIM = "/";
/** Path delimiting charachter */
public static final char PATH_DELIM_CHAR = PATH_DELIM.charAt(0);
/**
* Parses the Set-Cookie value into an array of <tt>Cookie</tt>s {@link Cookie}.
*
* @param host the host from which the <tt>Set-Cookie</tt> value was received
* @param port the port from which the <tt>Set-Cookie</tt> value was received
* @param path the path from which the <tt>Set-Cookie</tt> value was received
* @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was received over secure conection
* @param header the <tt>Set-Cookie</tt> received from the server
* @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
* @throws MalformedCookieException if an exception occurs during parsing
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Cookie[] parse(String host, int port, String path, boolean secure, final String header)
throws MalformedCookieException;
/**
* Parses the Set-Cookie header into an array of <tt>Cookie</tt>s {@link Cookie}.
*
* @param host the host from which the <tt>Set-Cookie</tt> header was received
* @param port the port from which the <tt>Set-Cookie</tt> header was received
* @param path the path from which the <tt>Set-Cookie</tt> header was received
* @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was received over secure conection
* @param header the <tt>Set-Cookie</tt> received from the server
* @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie header
* @throws MalformedCookieException if an exception occurs during parsing
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Cookie[] parse(String host, int port, String path, boolean secure, final Header header)
throws MalformedCookieException;
/**
* Performs cookie {@link Cookie} validation accoding to the cookie specification
*
* @param host the host from which the {@link Cookie} was received
* @param port the port from which the {@link Cookie} was received
* @param path the path from which the {@link Cookie} was received
* @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
* @throws MalformedCookieException if an exception occurs during validation
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public void validate(String host, int port, String path, boolean secure, final Cookie cookie)
throws MalformedCookieException;
/**
* Return <tt>true</tt> if the cookie should be submitted with a request with
* given attributes, <tt>false</tt> otherwise.
* @param host the host to which the request is being submitted
* @param port the port to which the request is being submitted (currenlty ignored)
* @param path the path to which the request is being submitted
* @param secure <tt>true</tt> if the request is using a secure connection
* @param cookie to be matched
* @return true if the cookie matches the criterium
*/
public boolean match(String host, int port, String path, boolean secure, final Cookie cookie);
/**
* Returns an array of <tt>Cookie</tt>s that should be submitted with a request with
* given attributes, <tt>false</tt> otherwise.
* @param host the host to which the request is being submitted
* @param port the port to which the request is being submitted (currenlty ignored)
* @param path the path to which the request is being submitted
* @param secure <tt>true</tt> if the request is using a secure protocol
* @param an array of <tt>Cookie</tt>s to be matched
* @return an array of <tt>Cookie</tt>s matching the criterium
*/
public Cookie[] match(String host, int port, String path, boolean secure, final Cookie cookies[]);
/**
* Return a string suitable for sending in a Cookie header
* @param cookie {@link Cookie} to be formatted as string
* @return a string suitable for sending in a Cookie header.
*/
public String formatCookie(Cookie cookie);
/**
* Creates a <tt>Cookie</tt> header value containing all cookies in <i>cookies</i>
* suitable for sending in a Cookie header
* @param an array of <tt>Cookie</tt>s to be formatted
* @return a string suitable for sending in a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public String formatCookies(Cookie[] cookies);
/**
* Creates a <tt>Cookie</tt> header containing all cookies in <i>cookies</i>,
* @param an array of <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
* @return a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Header formatCookieHeader(Cookie[] cookies);
/**
* Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
* @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
* @return a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Header formatCookieHeader(Cookie cookie);
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java
Index: CookieSpecBase.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Date;
import java.util.Locale;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* Cookie management functions shared by all specifications
* </p>
*
* @author B.C. Holmes
* @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
* @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
* @author Rod Waldhoff
* @author dIon Gillard
* @author Sean C. Sullivan
* @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
* @author Marc A. Saegesser
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
public class CookieSpecBase implements CookieSpec
{
/** Log object */
protected static final Log log = LogFactory.getLog(CookieSpec.class);
/** List of valid date formats for the "expires" cookie attribute. */
private static final DateFormat[] expiryFormats = {
// RFC 1123, 822, Date and time specification is English.
new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE dd-MMM-yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE dd-MMM-yyyy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE dd MMM yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, dd-MMM-yyyy HH-mm-ss z", Locale.US),
new SimpleDateFormat("EEE dd-MMM-yyyy HH-mm-ss z", Locale.US)
};
/** Default constructor */
public CookieSpecBase()
{
super();
}
/**
* Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
*
* <P>The syntax for the Set-Cookie response header is:
*
* <PRE>
* set-cookie = "Set-Cookie:" cookies
* cookies = 1#cookie
* cookie = NAME "=" VALUE * (";" cookie-av)
* NAME = attr
* VALUE = value
* cookie-av = "Comment" "=" value
* | "Domain" "=" value
* | "Max-Age" "=" value
* | "Path" "=" value
* | "Secure"
* | "Version" "=" 1*DIGIT
* </PRE>
*
* @param host the host from which the <tt>Set-Cookie</tt> value was received
* @param port the port from which the <tt>Set-Cookie</tt> value was received
* @param path the path from which the <tt>Set-Cookie</tt> value was received
* @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was received over secure conection
* @param header the <tt>Set-Cookie</tt> received from the server
* @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
* @throws MalformedCookieException if an exception occurs during parsing
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Cookie[] parse(String host, int port, String path, boolean secure, final String header)
throws MalformedCookieException
{
log.trace("enter CookieSpecBase.parse(String, port, path, boolean, Header)");
if(host == null)
{
throw new IllegalArgumentException("Host of origin may not be null");
}
if(host.trim().equals(""))
{
throw new IllegalArgumentException("Host of origin may not be blank");
}
if(port < 0)
{
throw new IllegalArgumentException("Invalid port: " + port);
}
if(path == null)
{
throw new IllegalArgumentException("Path of origin may not be null.");
}
if(header == null)
{
throw new IllegalArgumentException("Header may not be null.");
}
if (path.trim().equals("")) {
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)
{
defaultPath = defaultPath.substring(0, lastSlashIndex);
}
Cookie[] cookies = new Cookie[headerElements.length];
for (int i = 0; i < headerElements.length; i++) {
HeaderElement headerelement = headerElements[i];
Cookie cookie = new Cookie(host,
headerelement.getName(),
headerelement.getValue(),
defaultPath,
null,
false);
// cycle through the parameters
NameValuePair[] parameters = headerelement.getParameters();
// could be null. In case only a header element and no parameters.
if (parameters != null) {
for (int j = 0; j < parameters.length; j++) {
String param_name = parameters[j].getName().toLowerCase();
String param_value = parameters[j].getValue();
if (param_name.equals("version")) {
if (param_value == null) {
throw new MalformedCookieException("Missing value for version attribute");
}
try {
cookie.setVersion(Integer.parseInt(param_value));
} catch (NumberFormatException e) {
throw new MalformedCookieException( "Invalid version attribute: " + e.getMessage());
}
}
else if (param_name.equals("path")) {
if (param_value == null) {
throw new MalformedCookieException("Missing value for path attribute");
}
if (param_value.trim().equals("")) {
throw new MalformedCookieException("Blank value for path attribute");
}
cookie.setPath(param_value);
cookie.setPathAttributeSpecified(true);
}
else if (param_name.equals("domain")) {
if (param_value == null) {
throw new MalformedCookieException("Missing value for domain attribute");
}
if (param_value.trim().equals("")) {
throw new MalformedCookieException("Blank value for domain attribute");
}
cookie.setDomain(param_value);
cookie.setDomainAttributeSpecified(true);
}
else if (param_name.equals("max-age")) {
if (param_value == null) {
throw new MalformedCookieException("Missing value for max-age attribute");
}
int age;
try {
age = Integer.parseInt(param_value);
} catch (NumberFormatException e) {
throw new MalformedCookieException( "Invalid max-age attribute: " + e.getMessage());
}
cookie.setExpiryDate(new Date(System.currentTimeMillis() +
age * 1000L));
}
else if (param_name.equals("secure")) {
cookie.setSecure(true);
}
else if (param_name.equals("comment")) {
cookie.setComment(param_value);
}
else if (param_name.equals("expires")) {
if (param_value == null) {
throw new MalformedCookieException("Missing value for expires attribute");
}
boolean set = false;
// trim single quotes around expiry if present
// see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279
if(param_value.length() > 1 &&
param_value.startsWith("'") &&
param_value.endsWith("'")) {
param_value = param_value.substring(1,param_value.length()-1);
}
for(int k=0;k<expiryFormats.length;k++) {
try {
Date date = expiryFormats[k].parse(param_value);
cookie.setExpiryDate(date);
set = true;
break;
} catch (ParseException e) {
//Ignore and move on
}
}
if(!set) {
throw new MalformedCookieException("Unable to parse expiration date parameter: " + param_value);
}
}
}
}
cookies[i] = cookie;
}
return cookies;
}
/**
* Parses the Set-Cookie {@link Header} into an array of
* <tt>Cookie</tt>s.
*
* <P>The syntax for the Set-Cookie response header is:
*
* <PRE>
* set-cookie = "Set-Cookie:" cookies
* cookies = 1#cookie
* cookie = NAME "=" VALUE * (";" cookie-av)
* NAME = attr
* VALUE = value
* cookie-av = "Comment" "=" value
* | "Domain" "=" value
* | "Max-Age" "=" value
* | "Path" "=" value
* | "Secure"
* | "Version" "=" 1*DIGIT
* </PRE>
*
* @param host the host from which the <tt>Set-Cookie</tt> header was received
* @param port the port from which the <tt>Set-Cookie</tt> header was received
* @param path the path from which the <tt>Set-Cookie</tt> header was received
* @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was received over secure conection
* @param header the <tt>Set-Cookie</tt> received from the server
* @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie header
* @throws MalformedCookieException if an exception occurs during parsing
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Cookie[] parse(String host, int port, String path, boolean secure, final Header header)
throws MalformedCookieException
{
if(header == null)
{
throw new IllegalArgumentException("Header may not be null.");
}
return parse(host, port, path, secure, header.getValue());
}
/**
* Performs most fundamental cookie {@link Cookie} validation
*
* @param host the host from which the {@link Cookie} was received
* @param port the port from which the {@link Cookie} was received
* @param path the path from which the {@link Cookie} was received
* @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
* @throws MalformedCookieException if an exception occurs during validation
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
{
log.trace("enter CookieSpecBase.validate(String, port, path, boolean, Cookie)");
if(host == null)
{
throw new IllegalArgumentException("Host of origin may not be null");
}
if(host.trim().equals(""))
{
throw new IllegalArgumentException("Host of origin may not be blank");
}
if(port < 0)
{
throw new IllegalArgumentException("Invalid port: " + port);
}
if(path == null)
{
throw new IllegalArgumentException("Path of origin may not be null.");
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
host = host.toLowerCase();
// check version
if (cookie.getVersion() < 0)
{
throw new MalformedCookieException( "Illegal version number " + cookie.getValue());
}
// security check... we musn't allow the server to give us an
// invalid domain scope
// Validate the cookies domain attribute. NOTE: Domains without any dots are
// allowed to support hosts on private LANs that don't have DNS names. Since
// they have no dots, to domain-match the request-host and domain must be identical
// for the cookie to sent back to the origin-server.
if (host.indexOf(".") >= 0)
{
// Not required to have at least two dots. RFC 2965.
// A Set-Cookie2 with Domain=ajax.com will be accepted.
// domain must match host
if (!host.endsWith(cookie.getDomain()))
{
throw new MalformedCookieException(
"Illegal domain attribute \"" + cookie.getDomain() + "\". Domain of origin: \"" + host + "\"");
}
}
// another security check... we musn't allow the server to give us a
// cookie that doesn't match this path
if(!path.startsWith(cookie.getPath()))
{
throw new MalformedCookieException(
"Illegal path attribute \"" + cookie.getPath() + "\". Path of origin: \"" + path + "\"");
}
}
/**
* Return <tt>true</tt> if the cookie should be submitted with a request with
* given attributes, <tt>false</tt> otherwise.
* @param host the host to which the request is being submitted
* @param port the port to which the request is being submitted (currenlty ignored)
* @param path the path to which the request is being submitted
* @param secure <tt>true</tt> if the request is using a secure protocol
*/
public boolean match(String host, int port, String path, boolean secure, final Cookie cookie)
{
log.trace("enter CookieSpecBase.match(String, int, String, boolean, Cookie");
if(host == null)
{
throw new IllegalArgumentException("Host of origin may not be null");
}
if(host.trim().equals(""))
{
throw new IllegalArgumentException("Host of origin may not be blank");
}
if(port < 0)
{
throw new IllegalArgumentException("Invalid port: " + port);
}
if(path == null)
{
throw new IllegalArgumentException("Path of origin may not be null.");
}
if(cookie == null)
{
throw new IllegalArgumentException("Cookie may not be null");
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
host = host.toLowerCase();
if (cookie.getDomain() == null) {
log.warn("Invalid cookie state: domain not specified");
return false;
}
if (cookie.getPath() == null) {
log.warn("Invalid cookie state: path not specified");
return false;
}
return (
(cookie.getExpiryDate() == null ||
cookie.getExpiryDate().after(new Date())) && // only add the cookie if it hasn't yet expired
(domainMatch(host, cookie.getDomain())) && // and the domain pattern matches
(pathMatch(path, cookie.getPath())) && // and the path is null or matching
(cookie.getSecure() ? secure : true) // and if the secure flag is set, only if the request is actually secure
);
}
/**
* Performs a domain-match as described in RFC2109.
*/
private static boolean domainMatch(String host, String domain)
{
boolean match = host.equals(domain) ||
(domain.startsWith(".") && host.endsWith(domain));
return match;
}
/**
* Performs a path-match slightly smarter than a straight-forward startsWith check.
*/
private static boolean pathMatch(final String path, final String topmostPath)
{
boolean match = path.startsWith( topmostPath );
if (match) {
if (!topmostPath.endsWith(PATH_DELIM)) {
match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
}
}
return match;
}
/**
* Returns an array of <tt>Cookie</tt>s that should be submitted with a request with
* given attributes, <tt>false</tt> otherwise.
* @param host the host to which the request is being submitted
* @param port the port to which the request is being submitted (currenlty ignored)
* @param path the path to which the request is being submitted
* @param secure <tt>true</tt> if the request is using a secure protocol
* @param an array of <tt>Cookie</tt>s to be matched
* @return an array of <tt>Cookie</tt>s matching the criterium
*/
public Cookie[] match(String host, int port, String path, boolean secure, final Cookie cookies[])
{
log.trace("enter CookieSpecBase.match(String, int, String, boolean, Cookie[])");
if(host == null)
{
throw new IllegalArgumentException("Host of origin may not be null");
}
if(host.trim().equals(""))
{
throw new IllegalArgumentException("Host of origin may not be blank");
}
if(port < 0)
{
throw new IllegalArgumentException("Invalid port: " + port);
}
if(path == null)
{
throw new IllegalArgumentException("Path of origin may not be null.");
}
if(cookies == null)
{
throw new IllegalArgumentException("Cookie array may not be null");
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
host = host.toLowerCase();
StringBuffer value = new StringBuffer();
if (cookies.length <= 0) {
return null;
}
List matching = new LinkedList();
for(int i=0;i<cookies.length;i++)
{
if(cookies[i].matches(host, port, path, secure))
{
addInPathOrder(matching, cookies[i]);
}
}
return (Cookie[])matching.toArray(new Cookie[matching.size()]);
}
/**
* Adds the given cookie into the given list in descending path order. That is,
* more specific path to least specific paths. This may not be the fastest
* algorythm, but it'll work OK for the small number of cookies we're
* generally dealing with.
*
* @param list - the list to add the cookie to
* @param addCookie - the Cookie to add to list
*/
private static void addInPathOrder(List list, Cookie addCookie)
{
int i = 0;
for(i=0;i<list.size();i++){
Cookie c = (Cookie)list.get(i);
if(addCookie.compare(addCookie, c) > 0){
break;
}
}
list.add(i, addCookie);
}
/**
* Return a string suitable for sending in a Cookie header
* @param cookie {@link Cookie} to be formatted as string
* @return a string suitable for sending in a Cookie header.
*/
public String formatCookie(Cookie cookie)
{
log.trace("enter CookieSpecBase.toExternalForm(Cookie)");
if(cookie == null)
{
throw new IllegalArgumentException("Cookie may not be null");
}
StringBuffer buf = new StringBuffer();
buf.append(cookie.getName()).append("=").append(cookie.getValue());
return buf.toString();
}
/**
* Creates a <tt>Cookie</tt> header value containing all cookies in <i>cookies</i>
* suitable for sending in a Cookie header
* @param an array of <tt>Cookie</tt>s to be formatted
* @return a string suitable for sending in a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public String formatCookies(Cookie[] cookies)
throws IllegalArgumentException
{
log.trace("enter CookieSpecBase.formatCookies(Cookie[])");
if(cookies == null)
{
throw new IllegalArgumentException("Cookie array may not be null");
}
if(cookies.length == 0)
{
throw new IllegalArgumentException("Cookie array may not be empty");
}
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < cookies.length; i++)
{
if (i > 0)
{
buffer.append("; ");
}
buffer.append(formatCookie(cookies[i]));
}
return buffer.toString();
}
/**
* Creates a <tt>Cookie</tt> header containing all cookies in <i>cookies</i>,
* @param an array of <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
* @return a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Header formatCookieHeader(Cookie[] cookies)
{
log.trace("enter CookieSpecBase.formatCookieHeader(Cookie[])");
return new Header("Cookie", formatCookies(cookies));
}
/**
* Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
* @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
* @return a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Header formatCookieHeader(Cookie cookie)
{
log.trace("enter CookieSpecBase.formatCookieHeader(Cookie)");
return new Header("Cookie", formatCookie(cookie));
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/HttpMethodBase.java
Index: HttpMethodBase.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/HttpMethodBase.java,v 1.1 2002/12/08 06:09:46 jsdever Exp $
* $Revision: 1.1 $
* $Date: 2002/12/08 06:09:46 $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.cookie.CookieSpec;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* An abstract base implementation of {@link HttpMethod}.
* </p>
*
* <p>
* At minimum, subclasses will need to override
* </p>
*
* <dl>
* <dt>
* {@link #getName}
* </dt>
* <dd>
* to return the approriate name for this method
* </dd>
* </dl>
*
* <p>
* When a method's request may contain a body, subclasses will typically want
* to override:
* </p>
*
* <dl>
* <dt>
* {@link #getRequestContentLength}
* </dt>
* <dd>
* to indicate the length (in bytes) of that body
* </dd>
* <dt>
* {@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)}
* </dt>
* <dd>
* to write the body
* </dd>
* </dl>
*
* <p>
* When a method requires additional request headers, subclasses will typically
* want to override:
* </p>
*
* <dl>
* <dt>
* {@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
* </dt>
* <dd>
* to write those headers
* </dd>
* </dl>
*
* <p>
* When a method expects specific response headers, subclasses may want to
* override:
* </p>
*
* <dl>
* <dt>
* {@link #processResponseHeaders
* processResponseHeaders(HttpState,HttpConnection)}
* </dt>
* <dd>
* to handle those headers
* </dd>
* </dl>
*
*
* @version $Revision: 1.1 $ $Date: 2002/12/08 06:09:46 $
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
* @author Rodney Waldhoff
* @author Sean C. Sullivan
* @author <a href="mailto:dion@apache.org">dIon Gillard</a>
* @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
* @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
* @author Ortwin Gl�ck
*/
public abstract class HttpMethodBase implements HttpMethod {
//~ Static variables/initializers ������������������������������������������
/** Maximum number of redirects and authentications that will be followed */
private static int maxForwards = 100;
// -------------------------------------------------------------- Constants
/** Log object for this class. */
private static final Log log = LogFactory.getLog(HttpMethod.class);
/** Log for any wire messages. */
private static final Log wireLog = LogFactory.getLog("httpclient.wire");
/** The User-Agent header sent on every request. */
protected static final Header USER_AGENT;
static {
String agent = System.getProperties()
.getProperty("httpclient.useragent",
"Jakarta Commons-HttpClient/2.0M1");
USER_AGENT = new Header("User-Agent", agent);
}
//~ Instance variables �����������������������������������������������������
/** My request headers, if any. */
private Map requestHeaders = new HashMap();
/** The Status-Line from the response. */
private StatusLine statusLine = null;
/** My response headers, if any. */
private Map responseHeaders = new HashMap();
/** My response footers, if any. */
private Map responseFooters = null;
/** Realms that we tried to authenticate to */
private Set realms = null;
/** Proxy Realms that we tried to authenticate to */
private Set proxyRealms = null;
/** My request path. */
private String path = null;
/** My query string, if any. */
private String queryString = null;
/** The response body, assuming it has not be intercepted by a sub-class. */
private InputStream responseStream = null;
/** The connection that the response stream was read from. */
private HttpConnection responseConnection = null;
/** Buffer for the response */
private byte[] responseBody = null;
/** Whether or not the request body has been sent. */
private boolean bodySent = false;
/** Whether or not I should automatically follow redirects. */
private boolean followRedirects = false;
/** Whether or not I should automatically processs authentication. */
private boolean doAuthentication = true;
/** Whether or not I should use the HTTP/1.1 protocol. */
private boolean http11 = true;
/** True if we're in strict mode. */
private boolean strictMode = false;
/** Whether or not I have been executed. */
private boolean used = false;
/**
* The maximum number of attempts to attempt recovery from an
* HttpRecoverableException.
*/
private int maxRetries = 3;
/** Default content encoding chatset */
protected static final String DEFAULT_CHARSET = "ISO-8859-1";
//~ Constructors �����������������������������������������������������������
// ----------------------------------------------------------- Constructors
/**
* No-arg constructor.
*/
public HttpMethodBase() {
}
/**
* Path-specifying constructor.
*
* @param path my path which can include a query
*/
public HttpMethodBase(String path) {
int pa = path.indexOf("?");
if (pa < 0) { //its just a path
setPath(path);
} else { //its a path with a query
setPath(path.substring(0, pa));
setQueryString(path.substring(pa+1, path.length()));
}
}
//~ Methods ����������������������������������������������������������������
// ------------------------------------------- Property Setters and Getters
/**
* Obtain the name of this method, suitable for use in the "request line",
* for example <tt>GET</tt> or <tt>POST</tt>.
*
* @return the name of this method
*/
public abstract String getName();
/**
* Set whether or not I should automatically follow HTTP redirects (status
* code 302, etc.)
*
* @param followRedirects true to follow redirects, false otherwise
*/
public void setFollowRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
}
/**
* Whether or not I should automatically follow HTTP redirects (status code
* 302, etc.)
*
* @return <tt>true</tt> if I will automatically follow HTTP redirects
*/
public boolean getFollowRedirects() {
return this.followRedirects;
}
/**
* Set whether or not I should use the HTTP/1.1 protocol.
*
* @param http11 true to use HTTP/1.1, false to use 1.0
*/
public void setHttp11(boolean http11) {
this.http11 = http11;
}
/**
* Whether or not I should automatically process responses where
* authentication is required (status code 401, etc.)
*
* @return <tt>true</tt> if authentications will be processed automatically
* @since 2.0
*/
public boolean getDoAuthentication() {
return doAuthentication;
}
/**
* Set whether or not I should automatically process responses where
* authentication is required (status code 401, etc.)
*
* @param doAuthentication <tt>true</tt> to process authentications
* @since 2.0
*/
public void setDoAuthentication(boolean doAuthentication) {
this.doAuthentication = doAuthentication;
}
// ---------------------------------------------- Protected Utility Methods
/**
* Access to flag to determine if client should use
* HTTP/1.1 protocol.
*
* @return <tt>true</tt> if I should use the HTTP/1.1 protocol
*/
public boolean isHttp11() {
return http11;
}
/**
* Set the path part of my request.
*
* @param path the path to request
*/
public void setPath(String path) {
this.path = path;
}
/**
* Add the specified request header.
*
* If a header of the same name already exists, the new value will be
* appended onto the the existing value list.
* A <i>header</i> value of <code>null</code> will be ignored.
* Note that header-name matching is case insensitive.
*
* @param header the header to add to the request
*/
public void addRequestHeader(Header header) {
log.trace("HttpMethodBase.addRequestHeader(Header)");
if (header == null) {
log.debug("null header value ignored");
} else {
addRequestHeader(header.getName(), header.getValue());
}
}
/**
* adds a response footer to the internal list
*/
public void addResponseFooter(Header footer) {
if (responseFooters == null) responseFooters = new HashMap();
responseFooters.put(footer.getName().toLowerCase(), footer);
}
/**
* Get the path part of my request.
*
* @return the path to request or "/" if the path is blank.
*/
public String getPath() {
return (path == null || path.equals("")) ? "/" : path;
}
/**
* Sets the query string.
* The user must ensure that the string is properly URL encoded.
* URIUtil.encodeAll, URIUtil.encodeWithinQuery or URIUtil.encodeQuery can
* be used to encode parameter names and values.
* The query string should not start with the question mark character.
*
* @param queryString the query string
*/
public void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* Set my query string.
*
* @param params an array of {@link NameValuePair}s to add as query string
* parameterss
*/
public void setQueryString(NameValuePair[] params) {
log.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
StringBuffer buf = new StringBuffer();
boolean needAmp = false;
for (int i = 0; i < params.length; i++) {
if (params[i].getName() != null) {
if (needAmp) {
buf.append("&");
} else {
needAmp = true;
}
String queryName = null;
try {
queryName = URIUtil.encodeWithinQuery(params[i].getName());
} catch (URIException urie) {
log.error("encoding error within query name", urie);
queryName = params[i].getName();
}
buf.append(queryName).append("=");
if (params[i].getValue() != null) {
String queryValue = null;
try {
queryValue =
URIUtil.encodeWithinQuery(params[i].getValue());
} catch (URIException urie) {
log.error("encoding error within query value", urie);
queryValue = params[i].getValue();
}
buf.append(queryValue);
}
}
}
queryString = buf.toString();
}
/**
* Get my query string.
*
* @return The query string portion of the request
*/
public String getQueryString() {
return queryString;
}
/**
* Set the specified request header, overwriting any previous value. Note
* that header-name matching is case-insensitive.
*
* @param headerName the header's name
* @param headerValue the header's value
*/
public void setRequestHeader(String headerName, String headerValue) {
Header header = new Header(headerName, headerValue);
setRequestHeader(header);
}
/**
* Set the specified request header, overwriting any previous value. Note
* that header-name matching is case insensitive.
*
* @param header the header
*/
public void setRequestHeader(Header header) {
requestHeaders.put(header.getName().toLowerCase(), header);
}
/**
* Get the request header associated with the given name. Header name
* matching is case insensitive. <tt>null</tt> will be returned if either
* <i>headerName</i> is <tt>null</tt> or there is no matching header for
* <i>headerName</i>.
*
* @param headerName the header name to match
*
* @return the matching header
*/
public Header getRequestHeader(String headerName) {
return (headerName == null)
? null : (Header) (requestHeaders.get(headerName.toLowerCase()));
}
/**
* Provides access to the request headers.
*
* @return an array of my request headers.
*/
public Header[] getRequestHeaders() {
return (Header[]) (requestHeaders.values().toArray(
new Header[requestHeaders.size()]));
}
/**
* Convenience method top provide access to the status code.
*
* @return the status code associated with the latest response.
*/
public int getStatusCode() {
return statusLine.getStatusCode();
}
/**
* Provide access to the status line.
*
* @return the status line object from the latest response.
* @since 2.0
*/
public StatusLine getStatusLine() {
return statusLine;
}
/**
* Checks if response data is available.
* @return true if response data is available, false otherwise.
*/
private boolean responseAvailable() {
return (responseBody != null) || (responseStream != null);
}
/**
* Provide access to the response headers
*
* @return an array of my response headers.
*/
public Header[] getResponseHeaders() {
return (Header[]) (responseHeaders.values().toArray(
new Header[responseHeaders.size()]));
}
/**
* Get the response header associated with the given name. Header name
* matching is case insensitive. <tt>null</tt> will be returned if either
* <i>headerName</i> is <tt>null</tt> or there is no matching header for
* <i>headerName</i>.
*
* @param headerName the header name to match
*
* @return the matching header
*/
public Header getResponseHeader(String headerName) {
return (headerName == null)
? null
: (Header) (responseHeaders.get(headerName.toLowerCase()));
}
/**
* Return my response body, if any, as a byte array.
* Otherwise return <tt>null</tt>.
*/
public byte[] getResponseBody() {
if (responseBody == null) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
InputStream is = getResponseBodyAsStream();
byte[] buffer = new byte[10000];
int len;
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
responseBody = os.toByteArray();
setResponseStream(null);
log.debug("buffering response body");
} catch(IOException e) {
log.error("getResponseBody failed", e);
responseBody = null;
}
}
return responseBody;
}
/**
* Return my response body, if any, as an {@link InputStream}. Otherwise
* return <tt>null</tt>.
*
* @return the response body as an {@link InputStream}
*
* @throws IOException when there are errors obtaining the response
*/
public InputStream getResponseBodyAsStream() throws IOException {
if (responseStream != null) {
return responseStream;
}
if (responseBody != null) {
responseStream = new ByteArrayInputStream(responseBody);
log.debug("re-creating response stream from byte array");
return responseStream;
}
return null;
}
/**
* Gets the response body as a string.
*
* <b>Note:</b> The string conversion done on the data is done with the
* default character encoding. The use of this method may be non-portable.
*
* @return my response body, if any, as a {@link String}. Otherwise return
* <tt>null</tt>.
*/
public String getResponseBodyAsString() {
if (!responseAvailable()) {
return null;
}
String body;
try {
body = new String(getResponseBody(), getResponseCharSet());
}
catch(UnsupportedEncodingException e) {
if (log.isWarnEnabled()) {
log.warn("Unsupported request body charset: " + e.getMessage());
}
body = new String(getResponseBody());
}
return body;
}
/**
* Return an array of response footers.
* @return <tt>null</tt> if no footers are available
*/
public Header[] getResponseFooters() {
if (responseFooters == null) {
return null;
}
return (Header[])(responseFooters.values().toArray(
new Header[responseFooters.size()]));
}
/**
* Get the response footer associated with the given name.
* Footer name matching is case insensitive.
* <tt>null</tt> will be returned if either <i>footerName</i> is
* <tt>null</tt> or there is no matching header for <i>footerName</i>
* or there are no footers available.
* @param footerName the footer name to match
* @return the matching footer
*/
public Header getResponseFooter(String footerName) {
if (responseFooters == null) {
return null;
}
return (footerName == null) ? null :
(Header)(responseFooters.get(footerName.toLowerCase()));
}
protected void setResponseStream(InputStream responseStream) {
this.responseStream = responseStream;
}
/**
* Provide access to the status text
*
* @return the status text (or "reason phrase") associated with the latest
* response.
*/
public String getStatusText() {
return statusLine.getReasonPhrase();
}
/**
* Turns strict mode on or off. In strict mode (the default) we following
* the letter of RFC 2616, the Http 1.1 specification. If strict mode is
* turned off we attempt to violate the specification in the same way that
* most Http user agent's do (and many HTTP servers expect. NOTE:
* StrictMode is currently experimental and its functionlaity may change
* in the future.
*
* @param strictMode true for strict mode, false otherwise
*/
public void setStrictMode(boolean strictMode) {
this.strictMode = strictMode;
}
/**
* Returns the value of strictMode. NOTE: StrictMode is currently
* experimental and its functionlaity may change in the future.
*
* @return true if strict mode is enabled.
*/
public boolean isStrictMode() {
return strictMode;
}
/**
* Add the specified request header, NOT overwriting any previous value.
* Note that header-name matching is case insensitive.
*
* @param headerName the header's name
* @param headerValue the header's value
*/
public void addRequestHeader(String headerName, String headerValue) {
// "It must be possible to combine the multiple header fields into
// one "field-name: field-value" pair, without changing the
// semantics of the message, by appending each subsequent field-value
// to the first, each separated by a comma."
// - HTTP/1.0 (4.3)
Header header = getRequestHeader(headerName);
if (null == header) {
// header doesn't exist already, simply create with name and value
header = new Header(headerName, headerValue);
} else {
// header exists, add this value to the comma separated list
header.setValue(getNewHeaderValue(header, headerValue));
}
setRequestHeader(header);
}
/**
* Close the provided HTTP connection, if:
* http 1.0 and not using the 'connect' method, or
* http 1.1 and the Connection: close header is sent
*
* @param connection the HTTP connection to process
* Add the specified request header. If a header of the same name already
* exists, the new value will be appended onto the the existing value
* list. A <i>header</i> value of <code>null</code> will be ignored. Note
* that header-name matching is case insensitive.
*/
private void closeConnection(HttpConnection connection) {
if (shouldCloseConnection()) {
connection.close();
}
}
private boolean shouldCloseConnection() {
if (!http11) {
if (getName().equals(ConnectMethod.NAME) &&
(statusLine.getStatusCode() == HttpStatus.SC_OK)) {
log.debug("Will leave connection open for tunneling");
return false;
} else {
log.debug("Should close connection since using HTTP/1.0, " +
"ConnectMethod and status is OK");
return true;
}
} else {
Header connectionHeader = getResponseHeader("connection");
if (null != connectionHeader
&& "close".equalsIgnoreCase(connectionHeader.getValue())) {
log.debug("Should close connection since \"Connection: close\" header found.");
return true;
}
}
return false;
}
private void wrapResponseStream( HttpConnection connection ) {
if ( responseStream != null ) {
this.responseConnection = connection;
this.responseStream = new ResponseAutoReleaseInputStream(responseStream);
}
}
/**
* Execute this method. Note that we cannot currently support redirects
* that change the connection parameters (host, port, protocol) because
* we don't yet have a good way to get the new connection. For the time
* being, we just return the redirect response code, and allow the user
* agent to resubmit if desired.
*
* @param state {@link HttpState} information to associate with this
* request. Must be non-null.
* @param conn the{@link HttpConnection} to write to/read from. Must be
* non-null. Note that we cannot currently support redirects that
* change the HttpConnection parameters (host, port, protocol)
* because we don't yet have a good way to get the new connection.
* For the time being, we just return the 302 response, and allow
* the user agent to resubmit if desired.
*
* @return the integer status code if one was obtained, or <tt>-1</tt>
*
* @throws HttpException if an protocol exception occurs
* @throws HttpRecoverableException if too many redirects occur.
* @throws IOException if an I/O error occurs
* @throws NullPointerException if the state is null
*/
public int execute(HttpState state, HttpConnection conn)
throws HttpException, IOException, NullPointerException {
log.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
//TODO: This method is too large
//check some error conditions
if (null == state) {
throw new NullPointerException("HttpState parameter");
}
if (null == conn) {
throw new NullPointerException("HttpConnection parameter");
}
if (hasBeenUsed()) {
throw new HttpException("Already used, but not recycled.");
}
if (!validate()) {
throw new HttpException("Not valid");
}
//pre-emptively add the authorization header, if required.
Authenticator.authenticate(this, state);
if (conn.isProxied()) {
Authenticator.authenticateProxy(this, state);
}
//Set visited = new HashSet();
realms = new HashSet();
proxyRealms = new HashSet();
int forwardCount = 0; //protect from an infinite loop
while (forwardCount++ < maxForwards) {
if (log.isDebugEnabled()) {
log.debug("Execute loop try " + forwardCount);
}
//write the request and read the response, will retry
processRequest(state, conn);
//if SC_CONTINUE write the request body
writeRemainingRequestBody(state, conn);
int statusCode = statusLine.getStatusCode();
switch (statusCode) {
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
log.debug("Authorization required");
if (doAuthentication) { //process authentication response
//if the authentication is successful, return the statusCode
//otherwise, drop through the switch and try again.
if (processAuthenticationResponse(state, conn)) {
wrapResponseStream(conn);
return statusCode;
}
} else { //let the client handle the authenticaiton
wrapResponseStream(conn);
return statusCode;
}
break;
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_TEMPORARY_REDIRECT:
log.debug("Redirect required");
if (! processRedirectResponse(state, conn)) {
wrapResponseStream(conn);
return statusCode;
}
break;
default:
// neither an unauthorized nor a redirect response
wrapResponseStream(conn);
return statusCode;
} //end of switch
/*
Revisiting may be desired. We do not know about the server's internal state.
//check to see if we have visited this url before
if (visited.contains(generateVisitedKey(conn))) {
log.error("Link " + generateVisitedKey(conn) + "' revisited");
return statusCode;
}
visited.add(generateVisitedKey(conn));
*/
//close connection if required
closeConnection(conn);
if (conn.isOpen()) {
//throw away body to position the stream after the response
getResponseBodyAsString();
}
} //end of loop
wrapResponseStream(conn);
log.error("Narrowly avoided an infinite loop in execute");
throw new HttpRecoverableException("Maximum redirects ("+ maxForwards +") exceeded");
}
private boolean processRedirectResponse(HttpState state, HttpConnection conn) {
if (!getFollowRedirects()) {
log.info("Redirect requested but followRedirects is "
+ "disabled");
return false;
}
//get the location header to find out where to redirect to
Header locationHeader = getResponseHeader("location");
if (locationHeader == null) {
// got a redirect response, but no location header
log.error("Received redirect response " + getStatusCode()
+ " but no location header");
return false;
}
String location = locationHeader.getValue();
if (log.isDebugEnabled()) {
log.debug("Redirect requested to location '" + location
+ "'");
}
//rfc2616 demands the location value be a complete URI
//Location = "Location" ":" absoluteURI
URL redirectUrl = null;
URL currentUrl = null;
try {
currentUrl = new URL(conn.getProtocol().toLowerCase(),
conn.getHost(), conn.getPort(), "");
redirectUrl = new URL(location);
} catch (MalformedURLException e) {
if (isStrictMode()) {
log.warn("Redirected location '" + location +
"' is not acceptable in strict mode");
return false;
} else { //location is incomplete, use current values for defaults
try {
log.debug("Redirect URL is not absolute - parsing as relative");
redirectUrl = new URL(currentUrl, location);
} catch (MalformedURLException ex) {
log.warn("Redirected location '" + location
+ "' is malformed");
return false;
}
}
}
//check for redirect to a different protocol, host or port
try{
checkValidRedirect(currentUrl, redirectUrl);
} catch (HttpException ex) {
//log the error and let the client handle the redirect
log.warn(ex.getMessage());
return false;
}
//update the current location with the redirect location
setPath(redirectUrl.getPath());
setQueryString(redirectUrl.getQuery());
if (log.isDebugEnabled()) {
log.debug("Redirecting from '" + currentUrl.toExternalForm()
+ "' to '" + redirectUrl.toExternalForm());
}
return true;
}
/**
* Check for a valid redirect given the current conn and new url.
* Redirect to a different protocol, host or port are checked for validity.
*
* @param conn The existing HttpConnection
* @param url The new URL to redirect to
* @throws HttpException if the redirect is invalid
* @since 2.0
*/
private static void checkValidRedirect(URL currentUrl, URL redirectUrl)
throws HttpException {
log.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)");
String oldProtocol = currentUrl.getProtocol();
String newProtocol = redirectUrl.getProtocol();
if (! oldProtocol.equals(newProtocol)) {
throw new HttpException("Redirect from protocol " + oldProtocol
+ " to " + newProtocol + " is not supported");
}
String oldHost = currentUrl.getHost();
String newHost = redirectUrl.getHost();
if (! oldHost.equalsIgnoreCase(newHost)) {
throw new HttpException("Redirect from host " + oldHost
+ " to " + newHost + " is not supported");
}
int oldPort = currentUrl.getPort();
if (oldPort < 0) {
oldPort = getDefaultPort(oldProtocol);
}
int newPort = redirectUrl.getPort();
if (newPort < 0) {
newPort = getDefaultPort(newProtocol);
}
if (oldPort != newPort) {
throw new HttpException("Redirect from port " + oldPort
+ " to " + newPort + " is not supported");
}
}
/**
* Returns the default port for the given protocol.
*
* @param protocol currently only http and https are recognized
* @return the default port of the given protocol or -1 if the
* protocol is not recognized.
*
* @since 2.0
*
*/
private static int getDefaultPort(String protocol) {
String proto = protocol.toLowerCase().trim();
if (proto.equals("http")){
return 80;
}
else if (proto.equals("https")){
return 443;
}
return -1;
}
/**
* Whether the object has been used and not recycled.
*
* @return <tt>true</tt> if I have been {@link #execute executed} but not
* recycled.
*/
public boolean hasBeenUsed() {
return used;
}
/**
* Recycle this method so that it can be used again. All of my instances
* variables will be reset once this method has been called.
*/
public void recycle() {
log.trace("enter HttpMethodBase.recycle()");
releaseConnection();
path = null;
followRedirects = false;
doAuthentication = true;
queryString = null;
requestHeaders.clear();
responseHeaders.clear();
statusLine = null;
used = false;
http11 = true;
bodySent = false;
responseBody = null;
}
/**
* @see org.apache.commons.httpclient.HttpMethod#releaseConnection()
*
* @since 2.0
*/
public void releaseConnection() {
if ( responseConnection != null ) {
responseConnection.releaseConnection();
this.responseConnection = null;
this.responseStream = null;
}
}
/**
* Remove the request header associated with the given name. Note that
* header-name matching is case insensitive.
*
* @param headerName the header name
*/
public void removeRequestHeader(String headerName) {
requestHeaders.remove(headerName.toLowerCase());
}
// ---------------------------------------------------------------- Queries
/**
* Confirm that I am ready to execute.
*
* <p>
* This implementation always returns <tt>true</tt>.
* </p>
*
* @return <tt>true</tt>
*/
public boolean validate() {
return true;
}
/**
* Return the length (in bytes) of my request body, suitable for use in a
* <tt>Content-Length</tt> header.
*
* <p>
* Return <tt>-1</tt> when the content-length is unknown.
* </p>
*
* <p>
* This implementation returns <tt>0</tt>, indicating that the request has
* no body.
* </p>
*
* @return <tt>0</tt>, indicating that the request has no body.
*/
protected int getRequestContentLength() {
return 0;
}
/**
* Adds an <tt>Authorization</tt> request if needed, as long as no
* <tt>Authorization</tt> request header already exists.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addAuthorizationRequestHeader(HttpState state,
HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addAuthorizationRequestHeader("
+ "HttpState, HttpConnection)");
// add authorization header, if needed
if (getRequestHeader(Authenticator.WWW_AUTH_RESP) == null) {
Header wwwAuthenticateHeader = getResponseHeader(
Authenticator.WWW_AUTH);
if (null != wwwAuthenticateHeader) {
try {
Authenticator.authenticate(this, state);
} catch (HttpException e) {
// ignored
}
}
}
}
/**
* Adds a <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
* request header, as long as no <tt>Content-Length</tt> request header
* already exists.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addContentLengthRequestHeader(HttpState state,
HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addContentLengthRequestHeader("
+ "HttpState, HttpConnection)");
// add content length or chunking
int len = getRequestContentLength();
if (getRequestHeader("content-length") == null) {
if (0 < len) {
setRequestHeader("Content-Length", String.valueOf(len));
} else if (http11 && (len < 0)) {
setRequestHeader("Transfer-Encoding", "chunked");
}
}
}
/**
* Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
+ "HttpConnection)");
CookieSpec matcher = CookiePolicy.getDefaultSpec();
Cookie[] cookies = matcher.match(
conn.getHost(),
conn.getPort(),
getPath(),
conn.isSecure(),
state.getCookies());
if ((cookies != null) && (cookies.length > 0))
{
if (this.strictMode)
{
// If in strict mode put all cookie in one header
setRequestHeader(matcher.formatCookieHeader(cookies));
}
else
{
// If not in strict mode put each cookie in a separate header
for(int i = 0; i < cookies.length; i++)
{
setRequestHeader(matcher.formatCookieHeader(cookies[i]));
}
}
}
}
/**
* Adds a <tt>Host</tt> request header, as long as no <tt>Host</tt> request
* header already exists.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addHostRequestHeader(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
+ "HttpConnection)");
// Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
// applications to send the Host request-header.
// TODO: Add the ability to disable the sending of this header for
// HTTP/1.0 requests.
String host = conn.getHost();
int port = conn.getPort();
if (getRequestHeader("host") != null) {
log.debug(
"Request to add Host header ignored: header already added");
return;
}
if (isIpAddress(host)) {
log.debug("Adding empty Host request header: host is an ipaddress");
setRequestHeader("Host", "");
return;
}
if (log.isDebugEnabled()) {
log.debug("Adding Host request header");
}
//appends the port only if not using the default port for the protocol
if (conn.isSecure()) {
setRequestHeader("Host", (port == 443) ? host : host + ':' + port);
} else {
setRequestHeader("Host", (port == 80) ? host : host + ':' + port);
}
}
/**
* Adds a <tt>Proxy-Authorization</tt> request if needed, as long as no
* <tt>Proxy-Authorization</tt> request header already exists.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addProxyAuthorizationRequestHeader(HttpState state,
HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader("
+ "HttpState, HttpConnection)");
// add proxy authorization header, if needed
if (getRequestHeader(Authenticator.PROXY_AUTH_RESP) == null) {
Header wwwAuthenticateHeader = getResponseHeader(
Authenticator.PROXY_AUTH);
if (null != wwwAuthenticateHeader) {
try {
Authenticator.authenticateProxy(this, state);
} catch (HttpException e) {
// ignored
}
}
}
}
/**
* Populates the request headers map to with additional {@link Header
* headers} to be submitted to the given {@link HttpConnection}.
*
* <p>
* This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
* <tt>Cookie</tt>, <tt>Content-Length</tt>, <tt>Transfer-Encoding</tt>,
* and <tt>Authorization</tt> headers, when appropriate.
* </p>
*
* <p>
* Subclasses may want to override this method to to add additional
* headers, and may choose to invoke this implementation (via
* <tt>super</tt>) to add the "standard" headers.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} the headers will eventually be
* written to
* @throws IOException when an error occurs writing the request
* @throws HttpException when a HTTP protocol error occurs
*
* @see #writeRequestHeaders
*/
protected void addRequestHeaders(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
+ "HttpConnection)");
addUserAgentRequestHeader(state, conn);
addHostRequestHeader(state, conn);
addCookieRequestHeader(state, conn);
addAuthorizationRequestHeader(state, conn);
addProxyAuthorizationRequestHeader(state, conn);
addContentLengthRequestHeader(state, conn);
}
/**
* Adds a default <tt>User-Agent</tt> request header, as long as no
* <tt>User-Agent</tt> request header already exists.
*
* @param state the client state
* @param conn the {@link HttpConnection} the headers will eventually be
* written to
* @throws IOException when an error occurs writing the request
* @throws HttpException when a HTTP protocol error occurs
*/
protected void addUserAgentRequestHeader(HttpState state,
HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
+ "HttpConnection)");
if (getRequestHeader("user-agent") == null) {
setRequestHeader(HttpMethodBase.USER_AGENT);
}
}
/**
* Throws an {@link IllegalStateException} if used but not recycled.
*
* @throws IllegalStateException if the method has been used and not
* recycled
*/
protected void checkNotUsed() throws IllegalStateException {
if (used) {
throw new IllegalStateException("Already used.");
}
}
/**
* Throws an {@link IllegalStateException} if not used since last recycle.
*
* @throws IllegalStateException if not used
*/
protected void checkUsed() throws IllegalStateException {
if (!used) {
throw new IllegalStateException("Not Used.");
}
}
// ------------------------------------------------- Static Utility Methods
/**
* Generate an HTTP/S request line according to the specified attributes.
*
* @param connection the connection the request will be sent to
* @param name the method name generate a request for
* @param requestPath the path string for the request
* @param query the query string for the request
* @param protocol the protocol to use (e.g. HTTP/1.0)
*
* @return a line to send to the server that will fulfil the request
*/
protected static String generateRequestLine(HttpConnection connection,
String name, String requestPath, String query, String protocol) {
log.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
+ "String, String, String, String)");
StringBuffer buf = new StringBuffer();
String path = null;
try {
path = (requestPath == null) ? "/" : URIUtil.encodePath(requestPath);
} catch (URIException urie) {
log.error("URI path encoding error");
path = requestPath;
}
buf.append(path);
if (query != null) {
if (query.indexOf("?") != 0) {
buf.append("?");
}
String queryString = null;
queryString = (query == null) ? "/" : query;
buf.append(queryString);
}
if (!connection.isProxied() || connection.isTransparent()) {
return (name + " " + buf.toString() + " " + protocol + "\r\n");
} else {
if (connection.isSecure()) {
return (name + " https://" + connection.getHost()
+ ((443 == connection.getPort()
|| -1 == connection.getPort())
? "" : (":" + connection.getPort())) + buf.toString()
+ " " + protocol + "\r\n");
} else {
return (name + " http://" + connection.getHost()
+ ((80 == connection.getPort()
|| -1 == connection.getPort())
? "" : (":" + connection.getPort())) + buf.toString()
+ " " + protocol + "\r\n");
}
}
}
/**
* When this method is invoked, {@link #readResponseBody
* readResponseBody(HttpState,HttpConnection)} will have been invoked.
*
* <p>
* This implementation does nothing.
* </p>
*
* <p>
* Subclasses may want to override this method.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
*
* @see #readResponse
* @see #readResponseBody
*/
protected void processResponseBody(HttpState state, HttpConnection conn) {
}
/**
* When this method is invoked, the response headers map will have been
* populated with the response headers (in other words, {@link
* #readResponseHeaders readResponseHeaders(HttpState,HttpConnection)}
* will have been invoked).
*
* <p>
* This implementation will handle the <tt>Set-Cookie</tt> and
* <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
* the given {@link HttpState}.
* </p>
*
* <p>
* Subclasses may want to override this method to specially process
* additional headers, and/or invoke this method (via <tt>super</tt>) to
* process the <tt>Set-Cookie</tt> and <tt>Set-Cookie2</tt> headers.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
*
* @see #readResponse
* @see #readResponseHeaders
*/
protected void processResponseHeaders(HttpState state,
HttpConnection conn) {
log.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
+ "HttpConnection)");
// add cookies, if any
// should we set cookies?
Header setCookieHeader = getResponseHeader("set-cookie2");
if (null == setCookieHeader) { //ignore old-style if new is supported
setCookieHeader = getResponseHeader("set-cookie");
}
if (setCookieHeader == null) return;
try {
CookieSpec parser = CookiePolicy.getDefaultSpec();
Cookie[] cookies = parser.parse(
conn.getHost(),
conn.getPort(),
getPath(),
conn.isSecure(),
setCookieHeader);
for (int i = 0; i < cookies.length; i++)
{
Cookie cookie = cookies[i];
CookieSpec validator = CookiePolicy.getSpecByVersion(cookie.getVersion());
validator.validate(
conn.getHost(),
conn.getPort(),
getPath(),
conn.isSecure(),
cookie);
if (log.isDebugEnabled()) {
log.debug("Cookie accepted: \"" + validator.formatCookie(cookie) + "\"");
}
state.addCookie(cookie);
}
} catch (HttpException e) {
if (log.isWarnEnabled()) {
log.warn("Cookie rejected: \"" + setCookieHeader.getValue() + "\". " + e.getMessage());
}
}
}
/**
* When this method is invoked, the {@link #getStatusCode status code} and
* {@link #getStatusText status text} values will have been set (in other
* words, {@link #readStatusLine readStatusLine(HttpState,HttpConnection}
* will have been invoked).
*
* <p>
* Subclasses may want to override this method to respond to these value.
* This implementation does nothing.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
*
* @see #readResponse
* @see #readStatusLine
*/
protected void processStatusLine(HttpState state, HttpConnection conn) {
}
/**
* Reads the response from the given {@link HttpConnection}.
*
* <p>
* The response is written according to the following logic:
*
* <ol>
* <li>
* {@link #readStatusLine readStatusLine(HttpState,HttpConnection)} is
* invoked to read the request line.
* </li>
* <li>
* {@link #processStatusLine processStatusLine(HttpState,HttpConnection)}
* is invoked, allowing the method to respond to the status line if
* desired.
* </li>
* <li>
* {@link #readResponseHeaders
* readResponseHeaders(HttpState,HttpConnection} is invoked to read the
* associated headers.
* </li>
* <li>
* {@link #processResponseHeaders
* processResponseHeaders(HttpState,HttpConnection} is invoked, allowing
* the method to respond to the headers if desired.
* </li>
* <li>
* {@link #readResponseBody readResponseBody(HttpState,HttpConnection)} is
* invoked to read the associated body (if any).
* </li>
* <li>
* {@link #processResponseBody
* processResponseBody(HttpState,HttpConnection} is invoked, allowing the
* method to respond to the body if desired.
* </li>
* </ol>
*
* Subclasses may want to override one or more of the above methods to to
* customize the processing. (Or they may choose to override this method
* if dramatically different processing is required.)
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*/
protected void readResponse(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace(
"enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
readStatusLine(state, conn);
processStatusLine(state, conn);
readResponseHeaders(state, conn);
processResponseHeaders(state, conn);
readResponseBody(state, conn);
processResponseBody(state, conn);
}
/**
* Read the response body from the given {@link HttpConnection}.
*
* <p>
* The current implementation simply consumes the expected response body
* (according to the values of the <tt>Content-Length</tt> and
* <tt>Transfer-Encoding</tt> headers, if any).
* </p>
*
* <p>
* Subclasses may want to override this method to to customize the
* processing.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*
* @see #readResponse
* @see #processResponseBody
*/
protected void readResponseBody(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace(
"enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
setResponseStream(_readResponseBody(state, conn));
}
/**
* Read the response body from the given {@link HttpConnection}.
* <p>
* The current implementation returns an appropriate stream
* (according to the values of the
* <tt>Content-Length</tt> and <tt>Transfer-Encoding</tt>
* headers, if any).
* <p>
*
* @see #readResponse
* @see #processResponseBody
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
* @return InputStream to read the response body from
*/
private InputStream _readResponseBody(HttpState state, HttpConnection conn)
throws IOException {
log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
responseBody = null; // is this desired?
Header lengthHeader = getResponseHeader("Content-Length");
Header transferEncodingHeader = getResponseHeader("Transfer-Encoding");
InputStream is = conn.getResponseInputStream();
if (wireLog.isDebugEnabled()) {
is = new WireLogInputStream(is);
}
InputStream result = null;
// We use Transfer-Encoding if present and ignore Content-Length.
// RFC2616, 4.4 item number 3
if (null != transferEncodingHeader) {
if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
result = new ChunkedInputStream(is, this);
}
} else if (null != lengthHeader) {
// we're using this just in case the content length is duplicated
// i.e. '57, 57'
HeaderElement[] lengthElements = lengthHeader.getValues();
String lengthValue = null;
if ( lengthElements.length > 1 ) {
// looks like the content length header was duplicated. if so
// they won't be key=value pairs so we just want to get
// the name not the value (which should be null)
// take the first value and ignore any others
lengthValue = lengthElements[0].getName();
} else {
lengthValue = lengthHeader.getValue();
}
try {
int expectedLength = Integer.parseInt(lengthValue);
// FIXME: what if the content length is 0, perhaps we should
// just return an empty stream in that case
result = new ContentLengthInputStream(is, expectedLength);
} catch(NumberFormatException e) {
throw new HttpException(
"Unable to parse server response content length: '"
+ lengthValue + "'"
);
}
} else if(canResponseHaveBody(statusLine.getStatusCode())
&& !getName().equals(ConnectMethod.NAME)){
result = is;
}
if (result == null) {
return null;
}
if (shouldCloseConnection()) {
result = new AutoCloseInputStream(result, conn);
}
return result;
}
/**
* Read response headers from the given {@link HttpConnection}, populating
* the response headers map.
*
* <p>
* Subclasses may want to override this method to to customize the
* processing.
* </p>
*
* <p>
* "It must be possible to combine the multiple header fields into one
* "field-name: field-value" pair, without changing the semantics of the
* message, by appending each subsequent field-value to the first, each
* separated by a comma." - HTTP/1.0 (4.3)
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*
* @see #readResponse
* @see #processResponseHeaders
*/
protected void readResponseHeaders(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
+ "HttpConnection)");
responseHeaders.clear();
String name = null;
String value = null;
for (; ;) {
String line = conn.readLine();
if ((line == null) || (line.length() < 1)) {
break;
}
// Parse the header name and value
// Check for folded headers first
// Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
// discussion on folded headers
boolean isFolded = false;
if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) {
// we have continuation folded header
// so append value
isFolded = true;
value = line.substring(1).trim();
} else {
// Otherwise we should have normal HTTP header line
// Parse the header name and value
int colon = line.indexOf(":");
if (colon < 0) {
throw new HttpException("Unable to parse header: " + line);
}
name = line.substring(0, colon).trim();
value = line.substring(colon + 1).trim();
}
Header header = getResponseHeader(name);
if (null == header) {
header = new Header(name, value);
} else {
String oldvalue = header.getValue();
if (null != oldvalue) {
if (isFolded) {
// LWS becomes space plus extended value
header = new Header(name, oldvalue + " " + value);
} else {
// Append additional header value
header = new Header(name, oldvalue + ", " + value);
}
} else {
header = new Header(name, value);
}
}
setResponseHeader(header);
}
}
/**
* Read the status line from the given {@link HttpConnection}, setting my
* {@link #getStatusCode status code} and {@link #getStatusText status
* text}.
*
* <p>
* Subclasses may want to override this method to to customize the
* processing.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to read the response from
*
* @throws IOException when errors occur reading the status line
* @throws HttpException If there is no status line, the protocol is not
* recognised, if we are unable to parse the status code from the line,
* or there was no status text
* @throws HttpRecoverableException when the status line is null and the
* request should be retried
*
* @see StatusLine
*/
protected void readStatusLine(HttpState state, HttpConnection conn)
throws IOException, HttpRecoverableException, HttpException {
log.trace(
"enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
//read out the HTTP status string
String statusString = conn.readLine();
while ((statusString != null) && !statusString.startsWith("HTTP/")) {
statusString = conn.readLine();
}
if (statusString == null) {
// A null statusString means the connection was lost before we got a
// response. Try again.
throw new HttpRecoverableException("Error in parsing the status "
+ " line from the response: unable to find line starting with"
+ " \"HTTP/\"");
}
//create the status line from the status string
statusLine = new StatusLine(statusString);
//check for a valid HTTP-Version
String httpVersion = statusLine.getHttpVersion();
if (httpVersion.equals("HTTP/1.0")){
http11 = false;
} else if (httpVersion.equals("HTTP/1.1")){
http11 = true;
} else {
throw new HttpException("Unrecognized server protocol: '"
+ httpVersion + "'");
}
}
// ------------------------------------------------------ Protected Methods
/**
* <p>
* Writes my request to the given {@link HttpConnection}.
* </p>
*
* <p>
* The request is written according to the following logic:
* </p>
*
* <ol>
* <li>
* {@link #writeRequestLine writeRequestLine(HttpState, HttpConnection)} is
* invoked to write the request line.
* </li>
* <li>
* {@link #writeRequestHeaders writeRequestHeaders(HttpState,
* HttpConnection)} is invoked to write the associated headers.
* </li>
* <li>
* <tt>\r\n</tt> is sent to close the head part of the request.
* </li>
* <li>
* {@link #writeRequestBody writeRequestBody(HttpState, HttpConnection)} is
* invoked to write the body part of the request.
* </li>
* </ol>
*
* <p>
* Subclasses may want to override one or more of the above methods to to
* customize the processing. (Or they may choose to override this method
* if dramatically different processing is required.)
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to write the request to
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*/
protected void writeRequest(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace(
"enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
writeRequestLine(state, conn);
writeRequestHeaders(state, conn);
conn.writeLine(); // close head
bodySent = writeRequestBody(state, conn);
}
/**
* Write the request body to the given {@link HttpConnection}.
*
* <p>
* If an expectation is required, this method should ensure that it has
* been sent by checking the {@link #getStatusCode status code}.
* </p>
*
* <p>
* This method should return <tt>true</tt> if the request body was actually
* sent (or is empty), or <tt>false</tt> if it could not be sent for some
* reason (for example, expectation required but not present).
* </p>
*
* <p>
* This implementation writes nothing and returns <tt>true</tt>.
* </p>
*
* @param state the client state
* @param conn the connection to write to
*
* @return <tt>true</tt>
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*/
protected boolean writeRequestBody(HttpState state, HttpConnection conn)
throws IOException, HttpException {
return true;
}
/**
* Writes the request headers to the given {@link HttpConnection}.
*
* <p>
* This implementation invokes {@link #addRequestHeaders
* addRequestHeaders(HttpState,HttpConnection)}, and then writes each
* header to the request stream.
* </p>
*
* <p>
* Subclasses may want to override this method to to customize the
* processing.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to write to
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*
* @see #addRequestHeaders
* @see #getRequestHeaders
*/
protected void writeRequestHeaders(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
+ "HttpConnection)");
addRequestHeaders(state, conn);
Iterator it = requestHeaders.values().iterator();
while (it.hasNext()) {
conn.print(((Header) it.next()).toExternalForm());
}
}
/**
* Writes the "request line" to the given {@link HttpConnection}.
*
* <p>
* Subclasses may want to override this method to to customize the
* processing.
* </p>
*
* @param state the client state
* @param conn the {@link HttpConnection} to write to
* @throws IOException when i/o errors occur reading the response
* @throws HttpException when a protocol error occurs or state is invalid
*
* @see #generateRequestLine
*/
protected void writeRequestLine(HttpState state, HttpConnection conn)
throws IOException, HttpException {
log.trace(
"enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
String requestLine = getRequestLine(conn);
conn.print(requestLine);
}
/**
* Gets the request line that was sent to the http server.
* Consider making this public. Consider creating a new class
* RequestLine for this purpose.
*/
private String getRequestLine(HttpConnection conn) {
return HttpMethodBase.generateRequestLine(conn, getName(),
getPath(), getQueryString(), getHttpVersion());
}
/**
* Get the HTTP version.
*
* @return HTTP/1.1 if http11, HTTP/1.0 otherwise
*
* @since 2.0
*/
private String getHttpVersion() {
return (http11 ? "HTTP/1.1" : "HTTP/1.0");
}
/**
* Determines if the provided value is a valid IPv4 internet address.
*
* @param value - value to check
*
* @return boolean - true if value is valid, otherwise false
*/
private static boolean isIpAddress(String value) {
log.trace("enter HttpMethodBase.isIpAddress(String)");
value = value.trim();
// prevent input values of 127.0.0.1. or .127.0.0.1, etc.
if (value.startsWith(".") || value.endsWith(".")) {
return false;
}
StringTokenizer tokenizer = new StringTokenizer(value, ".");
if (tokenizer.countTokens() == 4) {
while (tokenizer.hasMoreTokens()) {
try {
int i = Integer.parseInt(tokenizer.nextToken());
if ((i < 0) || (i > 255)) {
// parsed section of address is not in the proper range
return false;
}
} catch (NumberFormatException nfe) {
return false;
}
}
} else {
// wrong number of tokens
return false;
}
return true;
}
/**
* "It must be possible to combine the multiple header fields into one
* "field-name: field-value" pair, without changing the semantics of the
* message, by appending each subsequent field-value to the first, each
* separated by a comma."
* //TODO: This method is trying to make up for deficiencies in Header.
*
* @param existingHeader the current header
* @param value DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private String getNewHeaderValue(Header existingHeader, String value) {
String existingValue = existingHeader.getValue();
if (existingValue == null) {
existingValue = "";
}
String newValue = value;
if (value == null) {
newValue = "";
}
return existingValue + ", " + newValue;
}
/**
* Sets the specified response header.
*
* @param header the header to set.
*
* @since 2.0
*/
private void setResponseHeader(Header header) {
if (header == null) {
return;
}
responseHeaders.put(header.getName().toLowerCase(), header);
}
/**
* Per RFC 2616 section 4.3, some response can never contain a message
* body.
*
* @param status - the HTTP status code
*
* @return true if the message may contain a body, false if it can not
* contain a message body
*/
private static boolean canResponseHaveBody(int status) {
log.trace("enter HttpMethodBase.canResponseHaveBody(int)");
boolean result = true;
if ((status >= 100 && status <= 199) || (status == 204)
|| (status == 304)) { // NOT MODIFIED
result = false;
}
return result;
}
/**
* Generates a key used for idenifying visited URLs.
*
* @param conn DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private String generateVisitedKey(HttpConnection conn) {
return conn.getHost() + ":" + conn.getPort() + "|"
+ generateRequestLine(conn, getName(), getPath(),
getQueryString(), getHttpVersion());
}
/**
* process a response that requires authentication
*
* @param state the current state
* @param connection the connection for communication
*
* @return true if the request has completed process, false if more
* attempts are needed
*/
private boolean processAuthenticationResponse(HttpState state,
HttpConnection connection) {
log.trace("enter HttpMethodBase.processAuthenticationResponse("
+ "HttpState, HttpConnection)");
int statusCode = statusLine.getStatusCode();
// handle authentication required
Header wwwauth = null;
Set realmsUsed = null;
switch (statusCode) {
case HttpStatus.SC_UNAUTHORIZED:
wwwauth = getResponseHeader(Authenticator.WWW_AUTH);
realmsUsed = realms;
break;
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
wwwauth = getResponseHeader(Authenticator.PROXY_AUTH);
realmsUsed = proxyRealms;
break;
}
boolean authenticated = false;
// if there was a header requesting authentication
if (null != wwwauth) {
String pathAndCreds = getPath() + ":" + wwwauth.getValue();
if (realmsUsed.contains(pathAndCreds)) {
if (log.isInfoEnabled()) {
log.info("Already tried to authenticate to \""
+ wwwauth.getValue() + "\" but still receiving "
+ statusCode + ".");
}
return true;
} else {
realmsUsed.add(pathAndCreds);
}
try {
//remove preemptive header and reauthenticate
switch (statusCode) {
case HttpStatus.SC_UNAUTHORIZED:
removeRequestHeader(Authenticator.WWW_AUTH_RESP);
authenticated = Authenticator.authenticate(this, state);
break;
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
removeRequestHeader(Authenticator.PROXY_AUTH_RESP);
authenticated = Authenticator.authenticateProxy(this,
state);
break;
}
} catch (HttpException httpe) {
log.warn(httpe.getMessage());
return true; // finished request
} catch (UnsupportedOperationException uoe) {
log.warn(uoe.getMessage());
//FIXME: should this return true?
}
if (!authenticated) {
// won't be able to authenticate to this challenge
// without additional information
log.debug("HttpMethodBase.execute(): Server demands "
+ "authentication credentials, but none are "
+ "available, so aborting.");
} else {
log.debug("HttpMethodBase.execute(): Server demanded "
+ "authentication credentials, will try again.");
// let's try it again, using the credentials
}
}
return !authenticated; // finished processing if we aren't authenticated
}
/**
* Write a request and read the response. Both the write to the server will
* be retried {@link #maxRetries} times if the operation fails with a
* HttpRecoverableException. The write will only be attempted if the read
* has succeeded.
*
* <p>
* The <i>used</i> is set to true if the write succeeds.
* </p>
*
* @param state the current state
* @param connection the connection for communication
*
* @throws HttpException when errors occur as part of the HTTP protocol
* conversation
* @throws IOException when an I/O error occurs communicating with the
* server
*
* @see writeRequest(HttpState,HttpConnection)
* @see readResponse(HttpState,HttpConnection)
*/
private void processRequest(HttpState state, HttpConnection connection)
throws HttpException, IOException {
log.trace(
"enter HttpMethodBase.processRequest(HttpState, HttpConnection)");
//try to do the write
int retryCount = 0;
do {
retryCount++;
if (log.isTraceEnabled()) {
log.trace("Attempt number " + retryCount + " to write request");
}
try {
if (!connection.isOpen()) {
log.debug("Opening the connection.");
connection.open();
}
writeRequest(state, connection);
used = true; //write worked, mark this method as used
break; //move onto the write
} catch (HttpRecoverableException httpre) {
if (log.isDebugEnabled()) {
log.debug("Closing the connection.");
}
connection.close();
log.info("Recoverable exception caught when writing request");
if (retryCount == maxRetries) {
log.warn(
"Attempt to write request has reached max retries: "
+ maxRetries);
throw httpre;
}
}
} while (retryCount <= maxRetries);
//try to do the read
try {
readResponse(state, connection);
} catch (HttpRecoverableException httpre) {
log.warn("Recoverable exception caught when reading response");
if (log.isDebugEnabled()) {
log.debug("Closing the connection.");
}
connection.close();
throw httpre;
}
//everything should be OK at this point
}
/**
* On a {@link HttpStatus#SC_CONTINUE continue}, if there are more request
* bytes to be sent, write them to the connection
*
* @param state the current state
* @param connection the connection for communication
*
* @throws HttpException when errors occur as part of the HTTP protocol
* conversation
* @throws IOException when an I/O error occurs communicating with the
* server
*/
private void writeRemainingRequestBody(HttpState state,
HttpConnection connection)
throws HttpException, IOException {
log.trace("enter writeRemainingRequestBody(HttpState, HttpConnection)");
if (HttpStatus.SC_CONTINUE == statusLine.getStatusCode()) {
if (!bodySent) {
bodySent = writeRequestBody(state, connection);
} else {
log.warn("Received status CONTINUE but the body has already "
+ "been sent");
// According to RFC 2616 this respose should be ignored
}
readResponse(state, connection);
}
}
protected static String getContentCharSet(Header contentheader)
{
log.trace("enter getContentCharSet( Header contentheader )");
String charset = null;
if (contentheader != null){
try {
HeaderElement values[] = contentheader.getValues();
// I expect only one header element to be there
// No more. no less
if (values.length == 1) {
NameValuePair param = values[0].getParameterByName("charset");
if (param != null) {
// If I get anything "funny" UnsupportedEncondingException will result
charset = param.getValue();
}
}
}
catch(HttpException e){
log.error(e);
}
}
if (charset == null) {
if (log.isDebugEnabled()) {
log.debug("Default charset used: " + DEFAULT_CHARSET);
}
charset = DEFAULT_CHARSET;
}
return charset;
}
public String getRequestCharSet() {
return getContentCharSet(getRequestHeader("Content-Type"));
}
public String getResponseCharSet() {
return getContentCharSet(getResponseHeader("Content-Type"));
}
/**
* Releases this connection from its connectionManager when the response has
* been read.
*/
private class ResponseAutoReleaseInputStream extends InputStream {
private InputStream is;
public ResponseAutoReleaseInputStream(InputStream is) {
this.is = is;
}
/**
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
is.close();
releaseConnection();
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
int b = is.read();
if ( b == -1 ) {
releaseConnection();
}
return b;
}
/**
* @see java.io.InputStream#read(byte, int, int)
*/
public int read(byte[] array, int off, int len) throws IOException {
int b = is.read(array, off, len);
if ( b == -1 ) {
releaseConnection();
}
return b;
}
/**
* @see java.io.InputStream#read(byte)
*/
public int read(byte[] array) throws IOException {
int b = is.read(array);
if ( b == -1 ) {
releaseConnection();
}
return b;
}
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/MalformedCookieException.java
Index: MalformedCookieException.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
/**
* Signals that a cookie is in some way invalid or illegal in a given
* context
*
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
import org.apache.commons.httpclient.HttpException;
public class MalformedCookieException extends HttpException
{
/**
* Creates a new HttpException.
*/
public MalformedCookieException()
{
super();
}
/**
* Creates a new HttpException with the specified message.
*
* @param message exception message
*/
public MalformedCookieException(String message)
{
super(message);
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/NetscapeDraftSpec.java
Index: NetscapeDraftSpec.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
import java.util.StringTokenizer;
import org.apache.commons.httpclient.Cookie;
/**
* <p>
* Netscape draft specific cookie management functions
* </p>
*
* @author B.C. Holmes
* @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
* @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
* @author Rod Waldhoff
* @author dIon Gillard
* @author Sean C. Sullivan
* @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
* @author Marc A. Saegesser
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
public class NetscapeDraftSpec extends CookieSpecBase
{
/** Default constructor */
public NetscapeDraftSpec()
{
super();
}
/**
* Performs Netscape draft spesific cookie {@link Cookie} validation
*
* @param host the host from which the {@link Cookie} was received
* @param port the port from which the {@link Cookie} was received
* @param path the path from which the {@link Cookie} was received
* @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
* @throws MalformedCookieException if an exception occurs during validation
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
{
log.trace("enterNetscapeDraftCookieProcessor RCF2109CookieProcessor.validate(Cookie)");
// Perform generic validation
super.validate(host, port, path, secure, cookie);
// Perform Netscape Cookie draft specific validation
if (host.indexOf(".") >= 0)
{
int domainParts = new StringTokenizer(cookie.getDomain(), ".").countTokens();
if (isSpecialDomain(cookie.getDomain()))
{
if(domainParts < 2)
{
throw new MalformedCookieException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification for special domains");
}
}
else
{
if(domainParts < 3)
{
throw new MalformedCookieException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification");
}
}
}
}
/**
* Checks if the given domain is in one of the seven special
* top level domains defined by the Netscape cookie specification.
*/
private static boolean isSpecialDomain(String domain)
{
String ucDomain = domain.toUpperCase();
if(ucDomain.endsWith(".COM") ||
ucDomain.endsWith(".EDU") ||
ucDomain.endsWith(".NET") ||
ucDomain.endsWith(".GOV") ||
ucDomain.endsWith(".MIL") ||
ucDomain.endsWith(".ORG") ||
ucDomain.endsWith(".INT")){
return true;
}
return false;
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java
Index: RFC2109Spec.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 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", "HttpClient", 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
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.Cookie;
/**
* <p>
* RFC 2109 specific cookie management functions
* </p>
*
* @author B.C. Holmes
* @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
* @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
* @author Rod Waldhoff
* @author dIon Gillard
* @author Sean C. Sullivan
* @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
* @author Marc A. Saegesser
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*
* @since 2.0
*/
public class RFC2109Spec extends CookieSpecBase
{
/** Default constructor */
public RFC2109Spec()
{
super();
}
public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
{
log.trace("enter RFC2109Spec.validate(String, int, String, boolean, Cookie)");
// Perform generic validation
super.validate(host, port, path, secure, cookie);
// Perform RFC 2109 specific validation
if (cookie.isDomainAttributeSpecified() && (!cookie.getDomain().equals(host)))
{
// domain must start with dot
if (!cookie.getDomain().startsWith("."))
{
throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot");
}
// domain must have at least one embedded dot
int dotIndex = cookie.getDomain().indexOf('.', 1);
if(dotIndex < 0 || dotIndex == cookie.getDomain().length()-1)
{
throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: domain must contain an embedded dot");
}
// host minus domain may not contain any dots
if (host.substring(0,
host.length() -
cookie.getDomain().length()).indexOf('.') != -1)
{
throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: host minus domain may not contain any dots");
}
}
}
/**
* Return a string suitable for sending in a Cookie header
* @param cookie {@link Cookie} to be formatted as string
* @return a string suitable for sending in a Cookie header.
*/
public String formatCookie(Cookie cookie)
{
log.trace("enter RFC2109Spec.formatCookie(Cookie)");
if(cookie == null)
{
throw new IllegalArgumentException("Cookie may not be null");
}
StringBuffer buf = new StringBuffer();
buf.append(cookie.getName());
buf.append("=\"");
buf.append(cookie.getValue());
buf.append("\"");
if (cookie.getDomain() != null && cookie.isDomainAttributeSpecified()) {
buf.append("; $Domain=\"");
buf.append(cookie.getDomain());
buf.append("\"");
}
if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
buf.append("; $Path=\"");
buf.append(cookie.getPath());
buf.append("\"");
}
return buf.toString();
}
/**
* Creates a RFC 2109 compliant <tt>Cookie</tt> header value containing all cookies
* in <i>cookies</i> suitable for sending in a Cookie header
* @param an array of <tt>Cookie</tt>s to be formatted
* @return a string suitable for sending in a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public String formatCookies(Cookie[] cookies)
{
log.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
String value = super.formatCookies(cookies);
int version = Integer.MAX_VALUE;
// Pick the lowerest common denominator
for (int i = 0; i < cookies.length; i++)
{
Cookie cookie = cookies[i];
if (cookie.getVersion() < version)
{
version = cookie.getVersion();
}
}
StringBuffer buffer = new StringBuffer();
buffer.append("$Version=\"");
buffer.append(version);
buffer.append("\"; ");
buffer.append(value);
return buffer.toString();
}
/**
* Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
* @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
* @return a Cookie header.
* @throws java.lang.IllegalArgumentException if an input parameter is illegal
*/
public Header formatCookieHeader(Cookie cookie)
{
log.trace("enter RFC2109Spec.formatCookieHeader(Cookie)");
String value = formatCookie(cookie);
StringBuffer buffer = new StringBuffer();
buffer.append("$Version=\"");
buffer.append(cookie.getVersion());
buffer.append("\"; ");
buffer.append(value);
return new Header("Cookie", buffer.toString());
}
}
1.15 +85 -23 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestCookie.java
Index: TestCookie.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestCookie.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- TestCookie.java 29 Nov 2002 19:03:02 -0000 1.14
+++ TestCookie.java 8 Dec 2002 06:09:46 -0000 1.15
@@ -70,6 +70,7 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Iterator;
+import org.apache.commons.httpclient.cookie.*;
/**
@@ -224,23 +225,8 @@
assertTrue("Secure",!parsed[0].getSecure());
assertEquals("Version",1,parsed[0].getVersion());
}
-/*
- public void testParseNoName() throws Exception {
- Header setCookie = new Header("Set-Cookie","=cookie-value");
- Cookie[] parsed = Cookie.parse("127.0.0.1","/",setCookie);
- assertEquals("Found 1 cookie.",1,parsed.length);
- assertEquals("Name","",parsed[0].getName());
- assertEquals("Value","cookie-value",parsed[0].getValue());
- assertTrue("Comment",null == parsed[0].getComment());
- assertTrue("ExpiryDate",null == parsed[0].getExpiryDate());
- //assertTrue("isToBeDiscarded",parsed[0].isToBeDiscarded());
- assertTrue("isPersistent",!parsed[0].isPersistent());
- assertEquals("Domain","127.0.0.1",parsed[0].getDomain());
- assertEquals("Path","/",parsed[0].getPath());
- assertTrue("Secure",!parsed[0].getSecure());
- assertEquals("Version",0,parsed[0].getVersion());
- }
-*/
+
+
public void testParseNoValue() throws Exception {
Header setCookie = new Header("Set-Cookie","cookie-name=");
Cookie[] parsed = Cookie.parse("127.0.0.1","/",setCookie);
@@ -685,9 +671,9 @@
/**
* Tests default constructor.
*/
- public void testDefaultConsttuctor() throws Exception {
+ public void testDefaultConsttuctor() {
Cookie dummy = new Cookie();
- assertEquals( dummy.toExternalForm(), "noname=null" );
+ assertEquals( "noname=\"null\"", dummy.toExternalForm() );
}
/**
@@ -704,5 +690,81 @@
fail("Unexpected exception: " + e.toString());
}
}
+
+ /**
+ * Tests if cookie constructor rejects cookie name containing blanks.
+ */
+ public void testInvalidCookieName() {
+ try
+ {
+ Cookie dummy = new Cookie("localhost", "invalid name", "cooke name may not have blanks");
+ fail("IllegalArgumentException must have been thrown");
+ }
+ catch(IllegalArgumentException e)
+ {
+ // Expected
+ }
+ }
+
+
+ /**
+ * Tests if cookie constructor rejects cookie name starting with $.
+ */
+ public void testInvalidCookieName2() {
+ try
+ {
+ Cookie dummy = new Cookie("localhost", "$invalid_name", "cooke name may not start with $");
+ fail("IllegalArgumentException must have been thrown");
+ }
+ catch(IllegalArgumentException e)
+ {
+ // Expected
+ }
+ }
+
+
+ /**
+ * Tests Netscape specific cookie formatting.
+ */
+
+ public void testNetscapeCookieFormatting() {
+ Header setCookie = new Header(
+ "Set-Cookie", "name=value; path=/; domain=.mydomain.com");
+ try {
+ CookieSpec parser = CookiePolicy.getSpecByPolicy(CookiePolicy.NETSCAPE_DRAFT);
+ Cookie[] cookies = parser.parse("myhost.mydomain.com", 80, "/", false, setCookie );
+ parser.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
+ String s = parser.formatCookie(cookies[0]);
+ assertEquals("name=value", s);
+ }
+ catch(HttpException e) {
+ e.printStackTrace();
+ fail("Unexpected exception: " + e.toString());
+ }
+ }
+
+
+ /**
+ * Tests RFC 2109 compiant cookie formatting.
+ */
+
+ public void testRFC2109CookieFormatting() {
+ Header setCookie = new Header(
+ "Set-Cookie", "name=\"value\"; path=\"/\"; domain=\".mydomain.com\"");
+ try {
+ CookieSpec parser = CookiePolicy.getSpecByPolicy(CookiePolicy.RFC2109);
+ Cookie[] cookies = parser.parse("myhost.mydomain.com", 80, "/", false, setCookie );
+ parser.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
+ String s = parser.formatCookie(cookies[0]);
+ assertEquals(s, "name=\"value\"; $Domain=\".mydomain.com\"; $Path=\"/\"");
+ }
+ catch(HttpException e) {
+ e.printStackTrace();
+ fail("Unexpected exception: " + e.toString());
+ }
+ }
+
+
+
}
1.5 +75 -51 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappCookie.java
Index: TestWebappCookie.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappCookie.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- TestWebappCookie.java 15 Mar 2002 22:52:07 -0000 1.4
+++ TestWebappCookie.java 8 Dec 2002 06:09:46 -0000 1.5
@@ -105,7 +105,8 @@
public void testSetCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set");
method.setUseDisk(false);
@@ -125,7 +126,7 @@
public void testSetCookiePost() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
PostMethod method = new PostMethod("/" + context + "/cookie/write");
method.addParameter("simple","set");
method.setUseDisk(false);
@@ -145,7 +146,8 @@
public void testSetCookiePut() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
PutMethod method = new PutMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set");
method.setRequestBody("data to be sent via http post");
@@ -165,7 +167,8 @@
public void testSetExpiredCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=unset");
method.setUseDisk(false);
@@ -183,7 +186,8 @@
public void testSetExpiredCookiePut() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
PutMethod method = new PutMethod("/" + context + "/cookie/write");
method.setQueryString("simple=unset");
method.setRequestBody("data to be sent via http post");
@@ -201,7 +205,8 @@
public void testSetUnsetCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set");
method.setUseDisk(false);
@@ -236,7 +241,8 @@
public void testSetMultiCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set&domain=set");
method.setUseDisk(false);
@@ -259,7 +265,8 @@
public void testSetMultiCookiePut() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
PutMethod method = new PutMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set&domain=set");
method.setRequestBody("data to be sent via http post");
@@ -282,7 +289,8 @@
public void testSendCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set");
method.setUseDisk(false);
@@ -308,14 +316,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method2.getStatusCode());
- assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value</tt></p>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
+ String s = method2.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
}
public void testMultiSendCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.setStrictMode(true);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("simple=set&domain=set");
method.setUseDisk(false);
@@ -344,15 +355,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method2.getStatusCode());
- assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
+ String s = method2.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
+ assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
}
public void testDeleteCookieGet() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
GetMethod method = new GetMethod("/" + context + "/cookie/write");
@@ -385,10 +398,11 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method2.getStatusCode());
- assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
+ String s = method2.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
+ assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
}
{
@@ -419,15 +433,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method4.getStatusCode());
- assertTrue(method4.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method4.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
- assertTrue(method4.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
+ String s = method4.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
}
}
public void testDeleteCookiePut() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
PutMethod method = new PutMethod("/" + context + "/cookie/write");
@@ -460,10 +476,11 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method2.getStatusCode());
- assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
- assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
- assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
+ String s = method2.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
+ assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
}
{
@@ -494,15 +511,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method4.getStatusCode());
- assertTrue(method4.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
- assertTrue(method4.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
- assertTrue(method4.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
+ String s = method4.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
}
}
public void testPathCookie1() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
GetMethod method = new GetMethod("/" + context + "/cookie/write");
@@ -531,15 +550,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method.getStatusCode());
- assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/</tt></p>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
+ String s = method.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s ,s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
}
}
public void testPathCookie2() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
GetMethod method = new GetMethod("/" + context + "/cookie/write");
@@ -568,16 +589,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method.getStatusCode());
- assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/" + context +"</tt></p>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
+ String s = method.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/" + context +"\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
}
}
public void testPathCookie3() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
-
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
GetMethod method = new GetMethod("/" + context + "/cookie/write");
method.setQueryString("path=/" + context + "/cookie");
@@ -605,15 +627,17 @@
fail("Unable to execute method : " + t.toString());
}
assertEquals(200,method.getStatusCode());
- assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/" + context + "/cookie</tt></p>") >= 0);
- assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
+ String s = method.getResponseBodyAsString();
+ assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
+ assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/" + context + "/cookie\"</tt></p>") >= 0);
+ assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
}
}
public void testPathCookie4() throws Exception {
HttpClient client = new HttpClient();
- client.startSession(host, port);
+ client.getHostConfiguration().setHost(host, port, "http");
+ client.setStrictMode(true);
{
GetMethod method = new GetMethod("/" + context + "/cookie/write");
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>