package com.developi.openntf;
/**
* Extended Cookie Snippet v1.00:
* (C) 2013 Serdar Basegmez
*
* This class is a derivative work of SimpleCookie class from Apache Shiro
* project licensed under Apache License V2.0.
*
* Apache Shiro
* Copyright 2008-2012 The Apache Software Foundation
*
* This product includes software developed at
* The Apache Software Foundation (http://www.apache.org/).
*
* Certain parts (StringUtils etc.) of the source code for this
* product was copied for simplicity and to reduce dependencies
* from the source code developed by the Spring Framework Project
* (http://www.springframework.org).
*
*/
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ibm.commons.util.StringUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class CookieEx extends Cookie {
/**
* Usage:
*
* This snippet contains a Java class extending the standard Cookie of J2EE. The problem
* of current JRE we are using in XPages is that it doesn't support HttpOnly cookies.
* HttpOnly cookies are very important for security practices. In summary, if you mark a
* cookie as HttpOnly, browser will not let the client-side JavaScript code to access the
* value of that cookie. So it's a suggested method to protect the content of a cookie
* against possible client-side attack such as XSS or malicious browser plugins.
*
* I have also seen many XPages developers having difficulties on removing cookies. To
* remove a cookie, you should add an expired cookie to the servlet response. This class
* contains an helper method to ease cookie removal.
*
* To add a new cookie:
*
* CookieEx c = new CookieEx("Test", "SomeValue"); // Create a cookie 'Test=SomeValue'
* c.setPath("/"); // Set its path. By default, it will be the relative path of your app.
* c.setMaxAge(120); // Set its max-age in seconds
* c.setSecure(true); // Set the cookie secure so it will only be submitted over HTTPS
* c.setHttpOnly(true); // Set the cookie HttpOnly so it will not be accessible from CSJS
* c.save(); // Save... It will be added to the response stream.
*
*
* To remove a cookie - Method 1:
*
* CookieEx.removeCookie("Test"); // Remove the cookie with the name 'Test' and default path.
* CookieEx.removeCookie("Test", "/"); // Remove the cookie with the name 'Test' and path '/'
*
* To remove a cookie - Method 2:
*
* CookieEx c=new CookieEx("Test"); // Create a cookie with name 'Test' (you may have the cookie)
* c.setPath("/"); // Set its path. Don't forget if you are not using the default path.
* c.remove(); // Remove... It will add an empty cookie with zero-age and browser will remove it.
*
*
* Remember, server side components cannot know the incoming cookies' path or domain values.
* So you should explicitly give the correct path if it's not the default one.
*
* No need to say these methods works only for XPages. Because it's using FacesContext to get the
* servlet request and response object. But there are also methods with appropriate parameters.
* So if you want to use it for servlets or plugins, you can remove methods commented as
* 'XPages Specific' and use the following ones:
*
* public void saveTo(HttpServletRequest request, HttpServletResponse response)
* public void removeFrom(HttpServletRequest request, HttpServletResponse response)
* public String readValue(HttpServletRequest request, HttpServletResponse ignored)
*
* Sorry for long comments here and no comments on the code :)
*
*/
public static final int DEFAULT_VERSION = -1;
protected static final String NAME_VALUE_DELIMITER = "=";
protected static final String ATTRIBUTE_DELIMITER = "; ";
protected static final long DAY_MILLIS = 86400000; //1 day = 86,400,000 milliseconds
protected static final String GMT_TIME_ZONE_ID = "GMT";
protected static final String COOKIE_DATE_FORMAT_STRING = "EEE, dd-MMM-yyyy HH:mm:ss z";
protected static final String COOKIE_HEADER_NAME = "Set-Cookie";
protected static final String PATH_ATTRIBUTE_NAME = "Path";
protected static final String EXPIRES_ATTRIBUTE_NAME = "Expires";
protected static final String MAXAGE_ATTRIBUTE_NAME = "Max-Age";
protected static final String DOMAIN_ATTRIBUTE_NAME = "Domain";
protected static final String VERSION_ATTRIBUTE_NAME = "Version";
protected static final String COMMENT_ATTRIBUTE_NAME = "Comment";
protected static final String SECURE_ATTRIBUTE_NAME = "Secure";
protected static final String HTTP_ONLY_ATTRIBUTE_NAME = "HttpOnly";
private boolean httpOnly;
public CookieEx(String name, String value) {
super(name, value);
this.httpOnly = false; // We are setting it false by default
}
public CookieEx(String name) {
this(name, "");
}
// Missing methods in Cookie
public boolean getHttpOnly() {
return httpOnly;
}
public void setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
}
private String calculatePath(HttpServletRequest request) {
String path = clean(getPath());
if (StringUtil.isEmpty(path)) {
path = clean(request.getContextPath());
}
if (path == null) {
path = "/";
}
return path;
}
private void addCookieHeader(HttpServletResponse response, String name, String value, String comment,
String domain, String path, int maxAge, int version,
boolean secure, boolean httpOnly) {
String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);
response.addHeader(COOKIE_HEADER_NAME, headerValue);
}
/*
* This implementation followed the grammar defined here for convenience:
* <a href="http://github.com/abarth/http-state/blob/master/notes/2009-11-07-Yui-Naruse.txt">Cookie grammar</a>.
*
* @return the 'Set-Cookie' header value for this cookie instance.
*/
private String buildHeaderValue(String name, String value, String comment,
String domain, String path, int maxAge, int version,
boolean secure, boolean httpOnly) {
if (StringUtil.isEmpty(name)) {
throw new IllegalStateException("Cookie name cannot be null/empty.");
}
StringBuilder sb = new StringBuilder(name).append(NAME_VALUE_DELIMITER);
if (StringUtil.isNotEmpty(value)) {
sb.append(value);
}
appendComment(sb, comment);
appendDomain(sb, domain);
appendPath(sb, path);
appendExpires(sb, maxAge);
appendVersion(sb, version);
appendSecure(sb, secure);
appendHttpOnly(sb, httpOnly);
return sb.toString();
}
private void appendComment(StringBuilder sb, String comment) {
if (StringUtil.isNotEmpty(comment)) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(COMMENT_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(comment);
}
}
private void appendDomain(StringBuilder sb, String domain) {
if (StringUtil.isNotEmpty(domain)) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(DOMAIN_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(domain);
}
}
private void appendPath(StringBuilder sb, String path) {
if (StringUtil.isNotEmpty(path)) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(PATH_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(path);
}
}
private void appendExpires(StringBuilder sb, int maxAge) {
// if maxAge is negative, cookie should should expire when browser closes
// Don't write the maxAge cookie value if it's negative - at least on Firefox it'll cause the
// cookie to be deleted immediately
// Write the expires header used by older browsers, but may be unnecessary
// and it is not by the spec, see http://www.faqs.org/rfcs/rfc2965.html
if (maxAge >= 0) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(MAXAGE_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(maxAge);
sb.append(ATTRIBUTE_DELIMITER);
Date expires;
if (maxAge == 0) {
//delete the cookie by specifying a time in the past (1 day ago):
expires = new Date(System.currentTimeMillis() - DAY_MILLIS);
} else {
//Value is in seconds. So take 'now' and add that many seconds, and that's our expiration date:
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, maxAge);
expires = cal.getTime();
}
String formatted = toCookieDate(expires);
sb.append(EXPIRES_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(formatted);
}
}
private void appendVersion(StringBuilder sb, int version) {
if (version > DEFAULT_VERSION) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(VERSION_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(version);
}
}
private void appendSecure(StringBuilder sb, boolean secure) {
if (secure) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(SECURE_ATTRIBUTE_NAME); //No value for this attribute
}
}
private void appendHttpOnly(StringBuilder sb, boolean httpOnly) {
if (httpOnly) {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(HTTP_ONLY_ATTRIBUTE_NAME); //No value for this attribute
}
}
private static String toCookieDate(Date date) {
TimeZone tz = TimeZone.getTimeZone(GMT_TIME_ZONE_ID);
DateFormat fmt = new SimpleDateFormat(COOKIE_DATE_FORMAT_STRING, Locale.US);
fmt.setTimeZone(tz);
return fmt.format(date);
}
private static Cookie getCookie(HttpServletRequest request, String cookieName) {
Cookie cookies[] = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie;
}
}
}
return null;
}
// Imported from org.apache.shiro.util.StringUtils
private static String clean(String in) {
String out = in;
if (in != null) {
out = in.trim();
if (out.equals("")) {
out = null;
}
}
return out;
}
public void saveTo(HttpServletRequest request, HttpServletResponse response) {
String name = getName();
String value = getValue();
String comment = getComment();
String domain = getDomain();
String path = calculatePath(request);
int maxAge = getMaxAge();
int version = getVersion();
boolean secure = getSecure();
boolean httpOnly = getHttpOnly();
addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
}
// Important! You need to set the correct path before trying to remove it.
public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
String name = getName();
String value = "deleteMe";
String comment = null; //don't need to add extra size to the response - comments are irrelevant for deletions
String domain = getDomain();
String path = calculatePath(request);
int maxAge = 0; //always zero for deletion
int version = getVersion();
boolean secure = getSecure();
boolean httpOnly = false; //no need to add the extra text, plus the value 'deleteMe' is not sensitive at all
addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
}
public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
String name = getName();
String value = null;
javax.servlet.http.Cookie cookie = getCookie(request, name);
if (cookie != null) {
value = cookie.getValue();
}
return value;
}
// XPages-specific methods
private HttpServletRequest extractRequest() {
FacesContext context=FacesContext.getCurrentInstance();
ExternalContext extContext=context.getExternalContext();
return (HttpServletRequest) extContext.getRequest();
}
private HttpServletResponse extractResponse() {
FacesContext context=FacesContext.getCurrentInstance();
ExternalContext extContext=context.getExternalContext();
return (HttpServletResponse) extContext.getResponse();
}
public void save() {
saveTo(extractRequest(), extractResponse());
}
public void remove() {
removeFrom(extractRequest(), extractResponse());
}
public String readValue() {
return readValue(extractRequest(), extractResponse());
}
public static void removeCookie(String name) {
removeCookie(name, "");
}
public static void removeCookie(String name, String path) {
CookieEx c=new CookieEx(name);
c.setPath(path);
c.remove();
}
// end of XPages specific methods
}