001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.terracotta.session.util;
006:
007: import com.terracotta.session.SessionId;
008:
009: import java.io.IOException;
010: import java.net.MalformedURLException;
011: import java.net.URL;
012: import java.net.URLEncoder;
013:
014: import javax.servlet.http.Cookie;
015: import javax.servlet.http.HttpServletRequest;
016: import javax.servlet.http.HttpServletResponse;
017: import javax.servlet.http.HttpSession;
018:
019: public class DefaultCookieWriter implements SessionCookieWriter {
020:
021: protected static final int HTTP_PORT = 80;
022: protected static final int HTTPS_PORT = 443;
023:
024: protected final String cookieName;
025: protected final String idTag;
026: protected final boolean isTrackingEnabled;
027: protected final boolean isCookieEnabled;
028: protected final boolean isUrlRewriteEnabled;
029: protected final String cookieDomain;
030: protected final String cookiePath;
031: protected final String cookieComment;
032: protected final int cookieMaxAge;
033: protected final boolean isCookieSecure;
034:
035: public static DefaultCookieWriter makeInstance(ConfigProperties cp) {
036: Assert.pre(cp != null);
037: return new DefaultCookieWriter(cp.getSessionTrackingEnabled(),
038: cp.getCookiesEnabled(), cp.getUrlRewritingEnabled(), cp
039: .getCookieName(), cp.getCookieDomain(), cp
040: .getCookiePath(), cp.getCookieCoomment(), cp
041: .getCookieMaxAgeSeconds(), cp.getCookieSecure());
042: }
043:
044: protected DefaultCookieWriter(boolean isTrackingEnabled,
045: boolean isCookieEnabled, boolean isUrlRewriteEnabled,
046: String cookieName, String cookieDomain, String cookiePath,
047: String cookieComment, int cookieMaxAge,
048: boolean isCookieSecure) {
049: Assert
050: .pre(cookieName != null
051: && cookieName.trim().length() > 0);
052: Assert.pre(cookieMaxAge >= -1);
053: this .isTrackingEnabled = isTrackingEnabled;
054: this .isCookieEnabled = isCookieEnabled;
055: this .isUrlRewriteEnabled = isUrlRewriteEnabled;
056: this .cookieName = cookieName;
057: this .cookiePath = cookiePath;
058: this .cookieDomain = cookieDomain;
059: this .cookieComment = cookieComment;
060: this .cookieMaxAge = cookieMaxAge;
061: this .isCookieSecure = isCookieSecure;
062: this .idTag = ";" + this .cookieName.toLowerCase() + "=";
063: }
064:
065: public Cookie writeCookie(HttpServletRequest req,
066: HttpServletResponse res, SessionId id) {
067: Assert.pre(req != null);
068: Assert.pre(res != null);
069: Assert.pre(id != null);
070:
071: if (res.isCommitted()) {
072: throw new IllegalStateException(
073: "response is already committed");
074: }
075:
076: if (isTrackingEnabled && isCookieEnabled) {
077: Cookie rv = createCookie(req, id);
078: res.addCookie(rv);
079: return rv;
080: }
081:
082: return null;
083: }
084:
085: public String encodeRedirectURL(String url, HttpServletRequest req) {
086: Assert.pre(req != null);
087:
088: if (url == null || !isTrackingEnabled || !isUrlRewriteEnabled)
089: return url;
090: final String absolute = toAbsolute(url, req);
091: if (isEncodeable(absolute, req)) {
092: return toEncoded(url, req.getSession().getId(), idTag);
093: } else {
094: return url;
095: }
096: }
097:
098: public String encodeURL(String url, HttpServletRequest req) {
099: Assert.pre(req != null);
100:
101: if (url == null || !isTrackingEnabled || !isUrlRewriteEnabled)
102: return url;
103: String absolute = toAbsolute(url, req);
104: if (isEncodeable(absolute, req)) {
105: // W3c spec clearly said
106: if (url.equalsIgnoreCase("")) {
107: url = absolute;
108: }
109: return toEncoded(url, req.getSession().getId(), idTag);
110: } else {
111: return url;
112: }
113: }
114:
115: private static String toEncoded(final String url,
116: final String sessionId, final String idTag) {
117: Assert.pre(idTag != null);
118:
119: if ((url == null) || (sessionId == null))
120: return url;
121:
122: String path = url;
123: String query = "";
124: String anchor = "";
125: int question = url.indexOf('?');
126: if (question >= 0) {
127: path = url.substring(0, question);
128: query = url.substring(question);
129: }
130: int pound = path.indexOf('#');
131: if (pound >= 0) {
132: anchor = path.substring(pound);
133: path = path.substring(0, pound);
134: }
135: StringBuffer sb = new StringBuffer(path);
136: if (sb.length() > 0) { // jsessionid can't be first.
137: sb.append(idTag);
138: sb.append(sessionId);
139: }
140: sb.append(anchor);
141: sb.append(query);
142: return sb.toString();
143: }
144:
145: protected static boolean isEncodeable(final String location,
146: final HttpServletRequest hreq) {
147:
148: Assert.pre(hreq != null);
149:
150: if (location == null)
151: return false;
152:
153: // Is this an intra-document reference?
154: if (location.startsWith("#"))
155: return false;
156:
157: final HttpSession session = hreq.getSession(false);
158: if (session == null)
159: return false;
160: if (hreq.isRequestedSessionIdFromCookie())
161: return false;
162:
163: return isEncodeable(hreq, session, location);
164: }
165:
166: private static boolean isEncodeable(HttpServletRequest hreq,
167: HttpSession session, String location) {
168: Assert.pre(hreq != null);
169: Assert.pre(session != null);
170:
171: // Is this a valid absolute URL?
172: URL url = null;
173: try {
174: url = new URL(location);
175: } catch (MalformedURLException e) {
176: return false;
177: }
178:
179: // Does this URL match down to (and including) the context path?
180: if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
181: return false;
182: if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
183: return false;
184: final int serverPort = getPort(hreq);
185: int urlPort = getPort(url);
186: if (serverPort != urlPort)
187: return false;
188:
189: String contextPath = hreq.getContextPath();
190: if (contextPath != null) {
191: String file = url.getFile();
192: if ((file == null) || !file.startsWith(contextPath))
193: return false;
194: if (file.indexOf(";jsessionid=" + session.getId()) >= 0)
195: return false;
196: }
197:
198: // This URL belongs to our web application, so it is encodeable
199: return true;
200:
201: }
202:
203: private static int getPort(URL url) {
204: Assert.pre(url != null);
205: return getPort(url.getPort(), url.getProtocol());
206: }
207:
208: private static int getPort(HttpServletRequest hreq) {
209: Assert.pre(hreq != null);
210: return getPort(hreq.getServerPort(), hreq.getScheme());
211: }
212:
213: private static int getPort(final int port, final String scheme) {
214: if (port == -1) {
215: if ("https".equals(scheme))
216: return 443;
217: else
218: return 80;
219: }
220: return port;
221: }
222:
223: private static String toAbsolute(String location,
224: HttpServletRequest request) {
225: Assert.pre(request != null);
226: if (location == null)
227: return location;
228:
229: final boolean leadingSlash = location.startsWith("/");
230:
231: if (leadingSlash
232: || (!leadingSlash && (location.indexOf("://") == -1))) {
233: final StringBuffer sb = new StringBuffer();
234:
235: final String scheme = request.getScheme();
236: final String name = request.getServerName();
237: final int port = request.getServerPort();
238:
239: sb.append(scheme);
240: sb.append("://");
241: sb.append(name);
242: sb.append(getPortString(scheme, port));
243: if (!leadingSlash) {
244: final String relativePath = request.getRequestURI();
245: final int pos = relativePath.lastIndexOf('/');
246: final String frelativePath = relativePath.substring(0,
247: pos);
248: sb.append(encodeSafely(frelativePath));
249: sb.append('/');
250: }
251: sb.append(location);
252: return sb.toString();
253: } else {
254: return location;
255: }
256: }
257:
258: private static String getPortString(final String scheme,
259: final int port) {
260: if (("http".equals(scheme) && port != 80)
261: || ("https".equals(scheme) && port != 443)) {
262: return ":" + port;
263: } else {
264: return "";
265: }
266: }
267:
268: private static String encodeSafely(final String source) {
269: try {
270: return URLEncoder.encode(source, "UTF-8");
271: } catch (IOException e) {
272: return source;
273: }
274: }
275:
276: protected Cookie createCookie(HttpServletRequest req, SessionId id) {
277: Assert.pre(req != null);
278: Assert.pre(id != null);
279:
280: Cookie c = new Cookie(cookieName, id.getExternalId());
281: c.setPath(getCookiePath(req));
282: c.setMaxAge(cookieMaxAge);
283: c.setSecure(isCookieSecure);
284: if (cookieDomain != null)
285: c.setDomain(cookieDomain);
286: if (cookieComment != null)
287: c.setComment(cookieComment);
288:
289: Assert.post(c != null);
290: return c;
291: }
292:
293: protected String getCookiePath(HttpServletRequest req) {
294: Assert.pre(req != null);
295: if (cookiePath == null) {
296: return computeCookiePath(req);
297: }
298: return cookiePath;
299: }
300:
301: protected String computeCookiePath(HttpServletRequest req) {
302: // if nothing is specified, use request context path
303: String rv = req.getContextPath();
304: return rv == null || rv.trim().length() == 0 ? ConfigProperties.defaultCookiePath
305: : rv.trim();
306: }
307: }
|