blob: 59080868ba7cc0514ebef6b168ac9b330072c3e6 [file] [log] [blame]
/*
* Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.xml.internal.ws.transport.http.client;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* Generic class to hold onto HTTP cookies. Can record, retrieve, and
* persistently store cookies associated with particular URLs.
*
* @author WS Development Team
*/
public class CookieJar {
// The representation of cookies is relatively simple right now:
// a hash table with key being the domain and the value being
// a vector of cookies for that domain.
// REMIND: create this on demand in the future
private transient Hashtable<String,Vector<HttpCookie>> cookieJar = new Hashtable<String, Vector<HttpCookie>>();
/**
* Create a new, empty cookie jar.
*/
public CookieJar() {
}
/**
* Records any cookies which have been sent as part of an HTTP response.
* The connection parameter must be already have been opened, so that
* the response headers are available. It's ok to pass a non-HTTP
* URL connection, or one which does not have any set-cookie headers.
*/
public synchronized void recordAnyCookies(URLConnection connection) {
HttpURLConnection httpConn = (HttpURLConnection) connection;
String headerKey;
for (int hi = 1;
(headerKey = httpConn.getHeaderFieldKey(hi)) != null;
hi++) {
if (headerKey.equalsIgnoreCase("set-cookie")) {
String cookieValue = httpConn.getHeaderField(hi);
recordCookie(httpConn, cookieValue);
}
}
}
/**
* Create a cookie from the cookie, and use the HttpURLConnection to
* fill in unspecified values in the cookie with defaults.
*/
private void recordCookie(HttpURLConnection httpConn, String cookieValue) {
HttpCookie cookie = new HttpCookie(httpConn.getURL(), cookieValue);
// First, check to make sure the cookie's domain matches the
// server's, and has the required number of '.'s
String twodot[] = { "com", "edu", "net", "org", "gov", "mil", "int" };
String domain = cookie.getDomain();
if (domain == null) {
return;
}
domain = domain.toLowerCase();
String host = httpConn.getURL().getHost();
host = host.toLowerCase();
boolean domainOK = host.equals(domain);
if (!domainOK && host.endsWith(domain)) {
int dotsNeeded = 2;
for (int i = 0; i < twodot.length; i++) {
if (domain.endsWith(twodot[i])) {
dotsNeeded = 1;
}
}
int lastChar = domain.length();
for (;(lastChar > 0) && (dotsNeeded > 0); dotsNeeded--) {
lastChar = domain.lastIndexOf('.', lastChar - 1);
}
if (lastChar > 0) {
domainOK = true;
}
}
if (domainOK) {
recordCookie(cookie);
}
}
/**
* Record the cookie in the in-memory container of cookies. If there
* is already a cookie which is in the exact same domain with the
* exact same
*/
private void recordCookie(HttpCookie cookie) {
recordCookieToJar(cookie, cookieJar, true);
}
/**
* Adds a Cookie for a given URL to the Cookie Jar.
* <P>
* New connections to the given URL will include the Cookie.
* <P>
* It allows to add Cookie information, to the Cookie jar, received
* by other mean.
* <P>
*
* @param url the URL to bind the Cookie to.
*
* @param cookieHeader String defining the Cookie:
* <P>
* &lt;name&gt;=&lt;value&gt;[;expires=<WHEN>]
* [;path=<PATH>][;domain=<DOMAIN>][;secure]
* <P>
* Refer to <A HREF=
* "http://home.netscape.com/newsref/std/cookie_spec.htm">
* Netscape Cookie specification</A> for the complete documentation.
*
*/
private void setCookie(URL url, String cookieHeader) {
HttpCookie cookie = new HttpCookie(url, cookieHeader);
this.recordCookie(cookie);
}
//
// Records the given cookie to the desired jar. If doNotify is true,
// tell globals to inform interested parties. It *only* makes since for
// doNotify to be true if jar is the static jar (i.e. Cookies.cookieJar).
//
//
private void recordCookieToJar(
HttpCookie cookie,
Hashtable<String,Vector<HttpCookie>> jar,
boolean doNotify) {
if (shouldRejectCookie(cookie)) {
return;
}
String domain = cookie.getDomain().toLowerCase();
Vector<HttpCookie> cookieList = jar.get(domain);
if (cookieList == null) {
cookieList = new Vector<HttpCookie>();
}
if (addOrReplaceCookie(cookieList, cookie, doNotify)) {
jar.put(domain, cookieList);
}
}
/**
* Scans the vector of cookies looking for an exact match with the
* given cookie. Replaces it if there is one, otherwise adds
* one at the end. The vector is presumed to have cookies which all
* have the same domain, so the domain of the cookie is not checked.
* <p>
* If doNotify is true, we'll do a vetoable notification of changing the
* cookie. This <b>only</b> makes since if the jar being operated on
* is Cookies.cookieJar.
* <p>
* If this is called, it is assumed that the cookie jar is exclusively
* held by the current thread.
*
* @return true if the cookie is actually set
*/
private boolean addOrReplaceCookie(
Vector<HttpCookie> cookies,
final HttpCookie cookie,
boolean doNotify) {
int numCookies = cookies.size();
String path = cookie.getPath();
String name = cookie.getName();
HttpCookie replaced = null;
int replacedIndex = -1;
for (int i = 0; i < numCookies; i++) {
HttpCookie existingCookie = cookies.elementAt(i);
String existingPath = existingCookie.getPath();
if (path.equals(existingPath)) {
String existingName = existingCookie.getName();
if (name.equals(existingName)) {
// need to replace this one!
replaced = existingCookie;
replacedIndex = i;
break;
}
}
}
// Do the replace
if (replaced != null) {
cookies.setElementAt(cookie, replacedIndex);
} else {
cookies.addElement(cookie);
}
return true;
}
/**
* Predicate function which returns true if the cookie appears to be
* invalid somehow and should not be added to the cookie set.
*/
private boolean shouldRejectCookie(HttpCookie cookie) {
// REMIND: implement per http-state-mgmt Internet Draft
return false;
}
// ab oct/17/01 - added synchronized
public synchronized void applyRelevantCookies(URLConnection connection) {
this.applyRelevantCookies(connection.getURL(), connection);
}
private void applyRelevantCookies(URL url, URLConnection connection) {
HttpURLConnection httpConn = (HttpURLConnection) connection;
String host = url.getHost();
applyCookiesForHost(host, url, httpConn);
// REMIND: should be careful about IP addresses here.
int index;
while ((index = host.indexOf('.', 1)) >= 0) {
// trim off everything up to, and including the dot.
host = host.substring(index + 1);
applyCookiesForHost(host, url, httpConn);
}
}
/**
* Host may be a FQDN, or a partial domain name starting with a dot.
* Adds any cookies which match the host and path to the
* cookie set on the URL connection.
*/
private void applyCookiesForHost(
String host,
URL url,
HttpURLConnection httpConn) {
//System.out.println("X0"+cookieJar.size());
Vector<HttpCookie> cookieList = cookieJar.get(host);
if (cookieList == null) {
// Hax.debugln("no matching hosts" + host);
return;
}
//System.out.println("X1"+cookieList.size());
String path = url.getFile();
int queryInd = path.indexOf('?');
if (queryInd > 0) {
// strip off the part following the ?
path = path.substring(0, queryInd);
}
Enumeration<HttpCookie> cookies = cookieList.elements();
Vector<HttpCookie> cookiesToSend = new Vector<HttpCookie>(10);
while (cookies.hasMoreElements()) {
HttpCookie cookie = cookies.nextElement();
String cookiePath = cookie.getPath();
if (path.startsWith(cookiePath)) {
// larrylf: Actually, my documentation (from Netscape)
// says that /foo should
// match /foobar and /foo/bar. Yuck!!!
if (!cookie.hasExpired()) {
cookiesToSend.addElement(cookie);
}
/*
We're keeping this piece of commented out code around just in
case we decide to put it back. the spec does specify the above.
int cookiePathLen = cookiePath.length();
// verify that /foo does not match /foobar by mistake
if ((path.length() == cookiePathLen)
|| (path.length() > cookiePathLen &&
path.charAt(cookiePathLen) == '/')) {
// We have a matching cookie!
if (!cookie.hasExpired()) {
cookiesToSend.addElement(cookie);
}
}
*/
}
}
// Now, sort the cookies in most to least specific order
// Yes, its the deaded bubblesort!!
// (it should be a small vector, so perf is not an issue...)
if (cookiesToSend.size() > 1) {
for (int i = 0; i < cookiesToSend.size() - 1; i++) {
HttpCookie headC = cookiesToSend.elementAt(i);
String head = headC.getPath();
// This little excercise is a cheap way to get
// '/foo' to read more specfic then '/'
if (!head.endsWith("/")) {
head = head + "/";
}
for (int j = i + 1; j < cookiesToSend.size(); j++) {
HttpCookie scanC = cookiesToSend.elementAt(j);
String scan = scanC.getPath();
if (!scan.endsWith("/")) {
scan = scan + "/";
}
int headCount = 0;
int index = -1;
while ((index = head.indexOf('/', index + 1)) != -1) {
headCount++;
}
index = -1;
int scanCount = 0;
while ((index = scan.indexOf('/', index + 1)) != -1) {
scanCount++;
}
if (scanCount > headCount) {
cookiesToSend.setElementAt(headC, j);
cookiesToSend.setElementAt(scanC, i);
headC = scanC;
head = scan;
}
}
}
}
// And send the sorted cookies...
cookies = cookiesToSend.elements();
String cookieStr = null;
while (cookies.hasMoreElements()) {
HttpCookie cookie = cookies.nextElement();
if (cookieStr == null) {
cookieStr = cookie.getNameValue();
} else {
cookieStr = cookieStr + "; " + cookie.getNameValue();
}
}
if (cookieStr != null) {
httpConn.setRequestProperty("Cookie", cookieStr);
}
}
}