001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.utils;
007:
008: import java.net.HttpURLConnection;
009: import java.text.ParseException;
010: import java.text.SimpleDateFormat;
011: import java.util.Date;
012: import java.util.Locale;
013: import java.util.StringTokenizer;
014: import java.util.TimeZone;
015: import java.util.Vector;
016:
017: import javax.servlet.http.Cookie;
018:
019: import org.apache.commons.logging.Log;
020: import org.apache.commons.logging.LogFactory;
021:
022: /**
023: * CookieCutter is a utility class which stores, sends and
024: * receives cookies for a CWebProxy channel instance.
025: * It can be used in other classes where appropriate.
026: * All cookies which are sent from the proxied application
027: * (and therefore need to be sent back) are kept in a
028: * Vector.
029: */
030: public class CookieCutter {
031:
032: private static final Log log = LogFactory
033: .getLog(CookieCutter.class);
034:
035: private Vector cookies;
036: private boolean supportSetCookie2;
037:
038: /**
039: * Instantiates a new CookieCutter object.
040: */
041: public CookieCutter() {
042: cookies = new Vector();
043: supportSetCookie2 = false;
044: }
045:
046: /**
047: * Returns true if cookies need to be sent to proxied application.
048: */
049: public boolean cookiesExist() {
050: if (cookies.size() > 0)
051: return true;
052: return false;
053: }
054:
055: /**
056: * Sends a cookie header to origin server according to the Netscape
057: * specification.
058: *
059: * @param httpUrlConnect The HttpURLConnection handling this URL connection
060: * @param domain The domain value of the cookie
061: * @param path The path value of the cookie
062: * @param port The port value of the cookie
063: */
064: public void sendCookieHeader(HttpURLConnection httpUrlConnect,
065: String domain, String path, String port) {
066: Vector cookiesToSend = new Vector();
067: ChannelCookie cookie;
068: String cport = "";
069: boolean portOk = true;
070: for (int index = 0; index < cookies.size(); index++) {
071: cookie = (ChannelCookie) cookies.elementAt(index);
072: boolean isExpired;
073: Date current = new Date();
074: Date cookieExpiryDate = cookie.getExpiryDate();
075: if (cookieExpiryDate != null)
076: isExpired = cookieExpiryDate.before(current);
077: else
078: isExpired = false;
079: if (cookie.isPortSet()) {
080: cport = cookie.getPort();
081: portOk = false;
082: }
083: if (!cport.equals("")) {
084: if (cport.indexOf(port) != -1)
085: portOk = true;
086: }
087: if (domain.endsWith(cookie.getDomain())
088: && path.startsWith(cookie.getPath()) && portOk
089: && !isExpired)
090: cookiesToSend.addElement(cookie);
091: }
092: if (cookiesToSend.size() > 0) {
093: //put the cookies in the correct order to send to origin server
094: Vector cookiesInOrder = new Vector();
095: ChannelCookie c1;
096: ChannelCookie c2;
097: boolean flag;
098: outerloop: for (int i = 0; i < cookiesToSend.size(); i++) {
099: c1 = (ChannelCookie) cookiesToSend.elementAt(i);
100: flag = false;
101: if (cookiesInOrder.size() == 0)
102: cookiesInOrder.addElement(c1);
103: else {
104: for (int index = 0; index < cookiesInOrder.size(); index++) {
105: c2 = (ChannelCookie) cookiesInOrder
106: .elementAt(index);
107: if (c1.getPath().length() >= c2.getPath()
108: .length()) {
109: cookiesInOrder.insertElementAt(c1, index);
110: flag = true;
111: continue outerloop;
112: }
113: }
114: if (!flag)
115: cookiesInOrder.addElement(c1);
116: }
117: }
118: //send the cookie header
119: // **NOTE** This is NOT the syntax of the cookie header according
120: // to rfc 2965. Tested under Apache's Tomcat, the servlet engine
121: // treats the cookie attributes as separate cookies.
122: // This is the syntax according to the Netscape Cookie Specification.
123: String headerValue = "";
124: ChannelCookie c;
125: for (int i = 0; i < cookiesInOrder.size(); i++) {
126: c = (ChannelCookie) cookiesInOrder.elementAt(i);
127: if (i == 0)
128: headerValue = c.getName() + "=" + c.getValue();
129: else
130: headerValue = headerValue + "; " + c.getName()
131: + "=" + c.getValue();
132: }
133: if (!headerValue.equals("")) {
134: httpUrlConnect
135: .setRequestProperty("Cookie", headerValue);
136: }
137: }
138: }
139:
140: /**
141: * Parses the cookie headers and stores the cookies in the
142: * cookies Vector.
143: */
144: public void storeCookieHeader(HttpURLConnection httpUrlConnect,
145: String domain, String path, String port) {
146: // store any cookies sent by the channel in the cookie vector
147: int index = 1;
148: String header;
149: while ((header = httpUrlConnect.getHeaderFieldKey(index)) != null) {
150: if (supportSetCookie2) {
151: if (header.equalsIgnoreCase("set-cookie2"))
152: processSetCookie2Header(httpUrlConnect
153: .getHeaderField(index), domain, path, port);
154: } else {
155: if (header.equalsIgnoreCase("set-cookie2")) {
156: supportSetCookie2 = true;
157: processSetCookie2Header(httpUrlConnect
158: .getHeaderField(index), domain, path, port);
159: } else if (header.equalsIgnoreCase("set-cookie")) {
160: try {
161: processSetCookieHeader(httpUrlConnect
162: .getHeaderField(index), domain, path,
163: port);
164: } catch (ParseException e) {
165: log
166: .warn(
167: "CookieCutter: Cannot process Set Cookie header",
168: e);
169: }
170: }
171: }
172: index++;
173: }
174: }
175:
176: /**
177: * Processes the Cookie2 header.
178: *
179: * @param headerVal The value of the header
180: * @param domain The domain value of the cookie
181: * @param path The path value of the cookie
182: * @param port The port value of the cookie
183: */
184: private void processSetCookie2Header(String headerVal,
185: String domain, String path, String port) {
186: StringTokenizer headerValue = new StringTokenizer(headerVal,
187: ",");
188: StringTokenizer cookieValue;
189: ChannelCookie cookie;
190: String token;
191: while (headerValue.hasMoreTokens()) {
192: cookieValue = new StringTokenizer(headerValue.nextToken(),
193: ";");
194: token = cookieValue.nextToken();
195: if (token.indexOf("=") != -1) {
196: cookie = new ChannelCookie(token.substring(0, token
197: .indexOf("=")), token.substring(
198: token.indexOf("=") + 1).trim());
199: } else {
200: if (log.isDebugEnabled())
201: log
202: .debug("CWebProxy: Invalid Header: \"Set-Cookie2:"
203: + headerVal + "\"");
204: cookie = null;
205: }
206: // set max-age, path and domain of cookie
207: if (cookie != null) {
208: boolean ageSet = false;
209: boolean domainSet = false;
210: boolean pathSet = false;
211: boolean portSet = false;
212: while (cookieValue.hasMoreTokens()) {
213: token = cookieValue.nextToken();
214: if ((!ageSet && (token.indexOf("=") != -1))
215: && token.substring(0, token.indexOf("="))
216: .trim().equalsIgnoreCase("max-age")) {
217: cookie.setMaxAge(Integer.parseInt(token
218: .substring(token.indexOf("=") + 1)
219: .trim()));
220: ageSet = true;
221: } else if ((!domainSet && (token.indexOf("=") != -1))
222: && token.substring(0, token.indexOf("="))
223: .trim().equalsIgnoreCase("domain")) {
224: cookie.setDomain(token.substring(
225: token.indexOf("=") + 1).trim());
226: domainSet = true;
227: cookie.domainIsSet();
228: } else if ((!pathSet && (token.indexOf("=") != -1))
229: && token.substring(0, token.indexOf("="))
230: .trim().equalsIgnoreCase("path")) {
231: cookie.setPath(token.substring(
232: token.indexOf("=") + 1).trim());
233: pathSet = true;
234: cookie.pathIsSet();
235: } else if (!portSet
236: && token.toLowerCase().indexOf("port") != -1) {
237: if (token.indexOf("=") == -1)
238: cookie.setPort(port);
239: else
240: cookie.setPort(token.substring(
241: token.indexOf("=") + 1).trim());
242: portSet = true;
243: cookie.portIsSet();
244: }
245: }
246: if (!domainSet) {
247: cookie.setDomain(domain);
248: }
249: if (!pathSet) {
250: cookie.setPath(path);
251: }
252: // set the version attribute
253: cookie.setVersion(1);
254: // checks to see if this cookie should replace one already stored
255: for (int index = 0; index < cookies.size(); index++) {
256: ChannelCookie old = (ChannelCookie) cookies
257: .elementAt(index);
258: if (cookie.getName().equals(old.getName())) {
259: String newPath = cookie.getPath();
260: String newDomain = cookie.getDomain();
261: String oldPath = old.getPath();
262: String oldDomain = old.getDomain();
263: if (newDomain.equalsIgnoreCase(oldDomain)
264: && newPath.equals(oldPath))
265: cookies.removeElement(old);
266: }
267: }
268: // handles the max-age cookie attribute (according to rfc 2965)
269: int expires = cookie.getMaxAge();
270: if (expires < 0) {
271: // cookie persists until browser shutdown so add cookie to
272: // cookie vector
273: cookies.addElement(cookie);
274: } else if (expires == 0) {
275: // cookie is to be discarded immediately, do not store
276: } else {
277: // add the cookie to the cookie vector and then
278: // set the expiry date for the cookie
279: Date d = new Date();
280: cookie.setExpiryDate(new Date((long) d.getTime()
281: + (expires * 1000)));
282: cookies.addElement(cookie);
283: }
284: }
285: }
286: }
287:
288: /**
289: * Processes the Cookie header.
290: *
291: * @param headerVal The value of the header
292: * @param domain The domain value of the cookie
293: * @param path The path value of the cookie
294: * @param port The port value of the cookie
295: */
296: private void processSetCookieHeader(String headerVal,
297: String domain, String path, String port)
298: throws ParseException {
299: StringTokenizer cookieValue;
300: String token;
301: ChannelCookie cookie;
302: if (((headerVal.indexOf("Expires=") != -1) || (headerVal
303: .indexOf("expires=") != -1))
304: || (headerVal.indexOf("EXPIRES=") != -1)) {
305: // there is only one cookie (old netscape spec)
306: cookieValue = new StringTokenizer(headerVal, ";");
307: token = cookieValue.nextToken();
308: if (token.indexOf("=") != -1) {
309: cookie = new ChannelCookie(token.substring(0, token
310: .indexOf("=")), token.substring(
311: token.indexOf("=") + 1).trim());
312: } else {
313: if (log.isDebugEnabled())
314: log
315: .debug("CWebProxy: Invalid Header: \"Set-Cookie:"
316: + headerVal + "\"");
317: cookie = null;
318: }
319: // set max-age, path and domain of cookie
320: if (cookie != null) {
321: boolean ageSet = false;
322: boolean domainSet = false;
323: boolean pathSet = false;
324: while (cookieValue.hasMoreTokens()) {
325: token = cookieValue.nextToken();
326: if ((!ageSet && (token.indexOf("=") != -1))
327: && token.substring(0, token.indexOf("="))
328: .trim().equalsIgnoreCase("expires")) {
329: SimpleDateFormat f = new SimpleDateFormat(
330: "EEE, d-MMM-yyyy HH:mm:ss z",
331: Locale.ENGLISH);
332: f.setTimeZone(TimeZone.getTimeZone("GMT"));
333: f.setLenient(true);
334: Date date = f.parse(token.substring(
335: token.indexOf("=") + 1).trim());
336: Date current = new Date();
337: if (date != null) {
338: //set max-age for cookie
339: long l;
340: if (date.before(current))
341: //accounts for the case where max age is 0 and cookie
342: //should be discarded immediately
343: l = 0;
344: else
345: l = date.getTime() - current.getTime();
346: int exp = (int) l / 1000;
347: cookie.setMaxAge(exp);
348: ageSet = true;
349: }
350: }
351: if ((!domainSet && (token.indexOf("=") != -1))
352: && token.substring(0, token.indexOf("="))
353: .trim().equalsIgnoreCase("domain")) {
354: cookie.setDomain(token.substring(
355: token.indexOf("=") + 1).trim());
356: domainSet = true;
357: }
358: if ((!pathSet && (token.indexOf("=") != -1))
359: && token.substring(0, token.indexOf("="))
360: .trim().equalsIgnoreCase("path")) {
361: cookie.setPath(token.substring(
362: token.indexOf("=") + 1).trim());
363: pathSet = true;
364: }
365: }
366: if (!domainSet) {
367: cookie.setDomain(domain);
368: }
369: if (!pathSet) {
370: cookie.setPath(path);
371: }
372: // sets the version attribute of the cookie
373: cookie.setVersion(0);
374: // checks to see if this cookie should replace one already stored
375: for (int index = 0; index < cookies.size(); index++) {
376: ChannelCookie old = (ChannelCookie) cookies
377: .elementAt(index);
378: if (cookie.getName().equals(old.getName())) {
379: String newPath = cookie.getPath();
380: String newDomain = cookie.getDomain();
381: String oldPath = old.getPath();
382: String oldDomain = old.getDomain();
383: if (newDomain.equalsIgnoreCase(oldDomain)
384: && newPath.equals(oldPath))
385: cookies.removeElement(old);
386: }
387: }
388: // handles the max-age cookie attribute (according to rfc 2965)
389: int expires = cookie.getMaxAge();
390: if (expires < 0) {
391: // cookie persists until browser shutdown so add cookie to
392: // cookie vector
393: cookies.addElement(cookie);
394: } else if (expires == 0) {
395: // cookie is to be discarded immediately, do not store
396: } else {
397: // add the cookie to the cookie vector and then
398: // set the expiry date for the cookie
399: Date d = new Date();
400: cookie.setExpiryDate(new Date((long) d.getTime()
401: + (expires * 1000)));
402: cookies.addElement(cookie);
403: }
404: }
405: } else {
406: // can treat according to RCF 2965
407: processSetCookie2Header(headerVal, domain, path, port);
408: }
409: }
410:
411: /**
412: * This class is used by any channel receiving cookies from a
413: * backend application to store cookie information.
414: * ChannelCookie extends javax.servlet.http.Cookie
415: * and contains methods to query the cookie's attribute status.
416: *
417: */
418: private class ChannelCookie extends Cookie {
419:
420: protected String port = null;
421: protected boolean pathSet = false;
422: protected boolean domainSet = false;
423: protected boolean portSet = false;
424: protected Date expiryDate = null;
425:
426: public ChannelCookie(String name, String value) {
427: super (name, value);
428: }
429:
430: public void setExpiryDate(Date expiryDate) {
431: this .expiryDate = expiryDate;
432: }
433:
434: public Date getExpiryDate() {
435: return expiryDate;
436: }
437:
438: public String getPath() {
439: String path = super .getPath();
440: if (path.startsWith("\"") && path.endsWith("\""))
441: path = path.substring(1, path.length() - 1);
442: return path;
443: }
444:
445: public String getValue() {
446: String value = super .getValue();
447: if (value.startsWith("\"") && value.endsWith("\""))
448: value = value.substring(1, value.length() - 1);
449: return value;
450: }
451:
452: public void pathIsSet() {
453: pathSet = true;
454: }
455:
456: public void domainIsSet() {
457: domainSet = true;
458: }
459:
460: public void portIsSet() {
461: portSet = true;
462: }
463:
464: public void setPort(String port) {
465: this .port = port;
466: }
467:
468: public String getPort() {
469: return port;
470: }
471:
472: public boolean isPathSet() {
473: return pathSet;
474: }
475:
476: public boolean isDomainSet() {
477: return domainSet;
478: }
479:
480: public boolean isPortSet() {
481: return portSet;
482: }
483: }
484:
485: }
|