001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.transport.http.client;
038:
039: import java.net.HttpURLConnection;
040: import java.net.URL;
041: import java.net.URLConnection;
042: import java.util.Enumeration;
043: import java.util.Hashtable;
044: import java.util.Vector;
045:
046: /**
047: * Generic class to hold onto HTTP cookies. Can record, retrieve, and
048: * persistently store cookies associated with particular URLs.
049: *
050: * @author WS Development Team
051: */
052: public class CookieJar {
053:
054: // The representation of cookies is relatively simple right now:
055: // a hash table with key being the domain and the value being
056: // a vector of cookies for that domain.
057: // REMIND: create this on demand in the future
058: private transient Hashtable<String, Vector<HttpCookie>> cookieJar = new Hashtable<String, Vector<HttpCookie>>();
059:
060: /**
061: * Create a new, empty cookie jar.
062: */
063: public CookieJar() {
064: }
065:
066: /**
067: * Records any cookies which have been sent as part of an HTTP response.
068: * The connection parameter must be already have been opened, so that
069: * the response headers are available. It's ok to pass a non-HTTP
070: * URL connection, or one which does not have any set-cookie headers.
071: */
072:
073: public synchronized void recordAnyCookies(URLConnection connection) {
074:
075: HttpURLConnection httpConn = (HttpURLConnection) connection;
076: String headerKey;
077:
078: for (int hi = 1; (headerKey = httpConn.getHeaderFieldKey(hi)) != null; hi++) {
079: if (headerKey.equalsIgnoreCase("set-cookie")) {
080: String cookieValue = httpConn.getHeaderField(hi);
081:
082: recordCookie(httpConn, cookieValue);
083: }
084: }
085: }
086:
087: /**
088: * Create a cookie from the cookie, and use the HttpURLConnection to
089: * fill in unspecified values in the cookie with defaults.
090: */
091: private void recordCookie(HttpURLConnection httpConn,
092: String cookieValue) {
093:
094: HttpCookie cookie = new HttpCookie(httpConn.getURL(),
095: cookieValue);
096:
097: // First, check to make sure the cookie's domain matches the
098: // server's, and has the required number of '.'s
099: String twodot[] = { "com", "edu", "net", "org", "gov", "mil",
100: "int" };
101: String domain = cookie.getDomain();
102:
103: if (domain == null) {
104: return;
105: }
106:
107: domain = domain.toLowerCase();
108:
109: String host = httpConn.getURL().getHost();
110:
111: host = host.toLowerCase();
112:
113: boolean domainOK = host.equals(domain);
114:
115: if (!domainOK && host.endsWith(domain)) {
116: int dotsNeeded = 2;
117:
118: for (int i = 0; i < twodot.length; i++) {
119: if (domain.endsWith(twodot[i])) {
120: dotsNeeded = 1;
121: }
122: }
123:
124: int lastChar = domain.length();
125:
126: for (; (lastChar > 0) && (dotsNeeded > 0); dotsNeeded--) {
127: lastChar = domain.lastIndexOf('.', lastChar - 1);
128: }
129:
130: if (lastChar > 0) {
131: domainOK = true;
132: }
133: }
134:
135: if (domainOK) {
136: recordCookie(cookie);
137: }
138: }
139:
140: /**
141: * Record the cookie in the in-memory container of cookies. If there
142: * is already a cookie which is in the exact same domain with the
143: * exact same
144: */
145: private void recordCookie(HttpCookie cookie) {
146: recordCookieToJar(cookie, cookieJar, true);
147: }
148:
149: /**
150: * Adds a Cookie for a given URL to the Cookie Jar.
151: * <P>
152: * New connections to the given URL will include the Cookie.
153: * <P>
154: * It allows to add Cookie information, to the Cookie jar, received
155: * by other mean.
156: * <P>
157: *
158: * @param url the URL to bind the Cookie to.
159: *
160: * @param cookieHeader String defining the Cookie:
161: * <P>
162: * <name>=<value>[;expires=<WHEN>]
163: * [;path=<PATH>][;domain=<DOMAIN>][;secure]
164: * <P>
165: * Refer to <A HREF=
166: * "http://home.netscape.com/newsref/std/cookie_spec.htm">
167: * Netscape Cookie specification</A> for the complete documentation.
168: *
169: */
170: private void setCookie(URL url, String cookieHeader) {
171:
172: HttpCookie cookie = new HttpCookie(url, cookieHeader);
173:
174: this .recordCookie(cookie);
175: }
176:
177: //
178: // Records the given cookie to the desired jar. If doNotify is true,
179: // tell globals to inform interested parties. It *only* makes since for
180: // doNotify to be true if jar is the static jar (i.e. Cookies.cookieJar).
181: //
182: //
183: private void recordCookieToJar(HttpCookie cookie,
184: Hashtable<String, Vector<HttpCookie>> jar, boolean doNotify) {
185:
186: if (shouldRejectCookie(cookie)) {
187: return;
188: }
189:
190: String domain = cookie.getDomain().toLowerCase();
191: Vector<HttpCookie> cookieList = jar.get(domain);
192:
193: if (cookieList == null) {
194: cookieList = new Vector<HttpCookie>();
195: }
196:
197: if (addOrReplaceCookie(cookieList, cookie, doNotify)) {
198: jar.put(domain, cookieList);
199: }
200: }
201:
202: /**
203: * Scans the vector of cookies looking for an exact match with the
204: * given cookie. Replaces it if there is one, otherwise adds
205: * one at the end. The vector is presumed to have cookies which all
206: * have the same domain, so the domain of the cookie is not checked.
207: * <p>
208: * If doNotify is true, we'll do a vetoable notification of changing the
209: * cookie. This <b>only</b> makes since if the jar being operated on
210: * is Cookies.cookieJar.
211: * <p>
212: * If this is called, it is assumed that the cookie jar is exclusively
213: * held by the current thread.
214: *
215: * @return true if the cookie is actually set
216: */
217: private boolean addOrReplaceCookie(Vector<HttpCookie> cookies,
218: final HttpCookie cookie, boolean doNotify) {
219:
220: int numCookies = cookies.size();
221: String path = cookie.getPath();
222: String name = cookie.getName();
223: HttpCookie replaced = null;
224: int replacedIndex = -1;
225:
226: for (int i = 0; i < numCookies; i++) {
227: HttpCookie existingCookie = cookies.elementAt(i);
228: String existingPath = existingCookie.getPath();
229:
230: if (path.equals(existingPath)) {
231: String existingName = existingCookie.getName();
232:
233: if (name.equals(existingName)) {
234:
235: // need to replace this one!
236: replaced = existingCookie;
237: replacedIndex = i;
238:
239: break;
240: }
241: }
242: }
243:
244: // Do the replace
245: if (replaced != null) {
246: cookies.setElementAt(cookie, replacedIndex);
247: } else {
248: cookies.addElement(cookie);
249: }
250:
251: return true;
252: }
253:
254: /**
255: * Predicate function which returns true if the cookie appears to be
256: * invalid somehow and should not be added to the cookie set.
257: */
258: private boolean shouldRejectCookie(HttpCookie cookie) {
259:
260: // REMIND: implement per http-state-mgmt Internet Draft
261: return false;
262: }
263:
264: // ab oct/17/01 - added synchronized
265: public synchronized void applyRelevantCookies(
266: URLConnection connection) {
267: this .applyRelevantCookies(connection.getURL(), connection);
268: }
269:
270: private void applyRelevantCookies(URL url, URLConnection connection) {
271:
272: HttpURLConnection httpConn = (HttpURLConnection) connection;
273: String host = url.getHost();
274:
275: applyCookiesForHost(host, url, httpConn);
276:
277: // REMIND: should be careful about IP addresses here.
278: int index;
279:
280: while ((index = host.indexOf('.', 1)) >= 0) {
281:
282: // trim off everything up to, and including the dot.
283: host = host.substring(index + 1);
284:
285: applyCookiesForHost(host, url, httpConn);
286: }
287: }
288:
289: /**
290: * Host may be a FQDN, or a partial domain name starting with a dot.
291: * Adds any cookies which match the host and path to the
292: * cookie set on the URL connection.
293: */
294: private void applyCookiesForHost(String host, URL url,
295: HttpURLConnection httpConn) {
296:
297: //System.out.println("X0"+cookieJar.size());
298: Vector<HttpCookie> cookieList = cookieJar.get(host);
299:
300: if (cookieList == null) {
301:
302: // Hax.debugln("no matching hosts" + host);
303: return;
304: }
305:
306: //System.out.println("X1"+cookieList.size());
307: String path = url.getFile();
308: int queryInd = path.indexOf('?');
309:
310: if (queryInd > 0) {
311:
312: // strip off the part following the ?
313: path = path.substring(0, queryInd);
314: }
315:
316: Enumeration<HttpCookie> cookies = cookieList.elements();
317: Vector<HttpCookie> cookiesToSend = new Vector<HttpCookie>(10);
318:
319: while (cookies.hasMoreElements()) {
320: HttpCookie cookie = cookies.nextElement();
321: String cookiePath = cookie.getPath();
322:
323: if (path.startsWith(cookiePath)) {
324:
325: // larrylf: Actually, my documentation (from Netscape)
326: // says that /foo should
327: // match /foobar and /foo/bar. Yuck!!!
328: if (!cookie.hasExpired()) {
329: cookiesToSend.addElement(cookie);
330: }
331:
332: /*
333: We're keeping this piece of commented out code around just in
334: case we decide to put it back. the spec does specify the above.
335:
336:
337: int cookiePathLen = cookiePath.length();
338:
339: // verify that /foo does not match /foobar by mistake
340: if ((path.length() == cookiePathLen)
341: || (path.length() > cookiePathLen &&
342: path.charAt(cookiePathLen) == '/')) {
343:
344: // We have a matching cookie!
345:
346: if (!cookie.hasExpired()) {
347: cookiesToSend.addElement(cookie);
348: }
349: }
350: */
351: }
352: }
353:
354: // Now, sort the cookies in most to least specific order
355: // Yes, its the deaded bubblesort!!
356: // (it should be a small vector, so perf is not an issue...)
357: if (cookiesToSend.size() > 1) {
358: for (int i = 0; i < cookiesToSend.size() - 1; i++) {
359: HttpCookie headC = cookiesToSend.elementAt(i);
360: String head = headC.getPath();
361:
362: // This little excercise is a cheap way to get
363: // '/foo' to read more specfic then '/'
364: if (!head.endsWith("/")) {
365: head = head + "/";
366: }
367:
368: for (int j = i + 1; j < cookiesToSend.size(); j++) {
369: HttpCookie scanC = cookiesToSend.elementAt(j);
370: String scan = scanC.getPath();
371:
372: if (!scan.endsWith("/")) {
373: scan = scan + "/";
374: }
375:
376: int headCount = 0;
377: int index = -1;
378:
379: while ((index = head.indexOf('/', index + 1)) != -1) {
380: headCount++;
381: }
382:
383: index = -1;
384:
385: int scanCount = 0;
386:
387: while ((index = scan.indexOf('/', index + 1)) != -1) {
388: scanCount++;
389: }
390:
391: if (scanCount > headCount) {
392: cookiesToSend.setElementAt(headC, j);
393: cookiesToSend.setElementAt(scanC, i);
394:
395: headC = scanC;
396: head = scan;
397: }
398: }
399: }
400: }
401:
402: // And send the sorted cookies...
403: cookies = cookiesToSend.elements();
404:
405: String cookieStr = null;
406:
407: while (cookies.hasMoreElements()) {
408: HttpCookie cookie = cookies.nextElement();
409:
410: if (cookieStr == null) {
411: cookieStr = cookie.getNameValue();
412: } else {
413: cookieStr = cookieStr + "; " + cookie.getNameValue();
414: }
415: }
416:
417: if (cookieStr != null) {
418: httpConn.setRequestProperty("Cookie", cookieStr);
419: }
420: }
421: }
|