package railo.runtime.type.scope; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import railo.commons.date.DateTimeUtil; import railo.commons.lang.StringUtil; import railo.runtime.PageContext; import railo.runtime.config.Config; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.listener.ApplicationContext; import railo.runtime.net.http.ReqRspUtil; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.op.date.DateCaster; import railo.runtime.security.ScriptProtect; import railo.runtime.type.Collection; import railo.runtime.type.KeyImpl; import railo.runtime.type.Struct; import railo.runtime.type.dt.DateTime; import railo.runtime.type.dt.TimeSpan; import railo.runtime.type.util.KeyConstants; /** * Implementation of the Cookie scope */ public final class CookieImpl extends ScopeSupport implements Cookie,ScriptProtected { private static final long serialVersionUID = -2341079090783313736L; public static final int NEVER = 946626690; private HttpServletResponse rsp; private int scriptProtected=ScriptProtected.UNDEFINED; private Map<String,String> raw=new HashMap<String,String>(); private String charset; private static final Class[] IS_HTTP_ONLY_ARGS_CLASSES = new Class[]{}; private static final Object[] IS_HTTP_ONLY_ARGS = new Object[]{}; private static final Class[] SET_HTTP_ONLY_ARGS_CLASSES = new Class[]{boolean.class}; private static final Object[] SET_HTTP_ONLY_ARGS = new Object[]{Boolean.TRUE}; private static final int EXPIRES_NULL = -1; private static Method isHttpOnly; private static Method setHttpOnly; /** * constructor for the Cookie Scope */ public CookieImpl() { super(false,"cookie",SCOPE_COOKIE); } @Override public Object setEL(Collection.Key key, Object value) { try { return set(key,value); } catch (PageException e) { return null; } } public Object set(Collection.Key key, Object value) throws PageException { raw.remove(key.getLowerString()); if(Decision.isStruct(value)) { Struct sct = Caster.toStruct(value); int expires=Caster.toIntValue(sct.get(KeyConstants._expires,null),EXPIRES_NULL); Object val=sct.get(KeyConstants._value,null); boolean secure=Caster.toBooleanValue(sct.get(KeyConstants._secure,null),false); boolean httpOnly=Caster.toBooleanValue(sct.get(KeyConstants._httponly,null),false); String domain=Caster.toString(sct.get(KeyConstants._domain,null),null); String path=Caster.toString(sct.get(KeyConstants._path,null),null); boolean preserveCase=Caster.toBooleanValue(sct.get(KeyConstants._preservecase,null),false); Boolean encode=Caster.toBoolean(sct.get(KeyConstants._encode,null),null); if(encode==null)encode=Caster.toBoolean(sct.get(KeyConstants._encodevalue,Boolean.TRUE),Boolean.TRUE); setCookie(key, val, expires, secure, path, domain,httpOnly,preserveCase,encode.booleanValue()); } else setCookie(key,value,null,false,"/",null,false,false,true); return value; } private void set(Config config,javax.servlet.http.Cookie cookie) throws PageException { String name=StringUtil.toLowerCase(ReqRspUtil.decode(cookie.getName(),charset,false)); raw.put(name,cookie.getValue()); if(isScriptProtected()) super.set (KeyImpl.init(name),ScriptProtect.translate(dec(cookie.getValue()))); else super.set (KeyImpl.init(name),dec(cookie.getValue())); } @Override public void clear() { raw.clear(); Collection.Key[] keys = keys(); for(int i=0;i<keys.length;i++) { removeEL(keys[i],false); } } @Override public Object remove(Collection.Key key) throws PageException { raw.remove(key.getLowerString()); return remove(key, true); } public Object remove(Collection.Key key, boolean alsoInResponse) throws PageException { raw.remove(key.getLowerString()); Object obj=super.remove (key); if(alsoInResponse)removeCookie(key); return obj; } public Object removeEL(Collection.Key key) { return removeEL(key, true); } private Object removeEL(Collection.Key key, boolean alsoInResponse) { raw.remove(key.getLowerString()); Object obj=super.removeEL (key); if(obj!=null && alsoInResponse) removeCookie(key); return obj; } private void removeCookie(Collection.Key key) { javax.servlet.http.Cookie cookie=new javax.servlet.http.Cookie(key.getUpperString(),""); cookie.setMaxAge(0); cookie.setSecure(false); cookie.setPath("/"); rsp.addCookie(cookie); } @Override public void setCookie(Collection.Key key, Object value, Object expires, boolean secure, String path, String domain) throws PageException { setCookie(key, value, expires, secure, path, domain, false, false, true); } @Override public void setCookie(Collection.Key key, Object value, int expires, boolean secure, String path, String domain) throws PageException { setCookie(key, value, expires, secure, path, domain, false, false, true); } @Override public void setCookieEL(Collection.Key key, Object value, int expires, boolean secure, String path, String domain) { setCookieEL(key, value, expires, secure, path, domain, false, false, true); } @Override public void setCookie(Collection.Key key, Object value, Object expires, boolean secure, String path, String domain, boolean httpOnly, boolean preserveCase, boolean encode) throws PageException { int exp=EXPIRES_NULL; // expires if(expires==null) { exp=EXPIRES_NULL; } else if(expires instanceof Date) { exp=toExpires((Date)expires); } else if(expires instanceof TimeSpan) { exp=toExpires((TimeSpan)expires); } else if(expires instanceof Number) { exp=toExpires((Number)expires); } else if(expires instanceof String) { exp=toExpires((String)expires); } else { throw new ExpressionException("invalid type ["+Caster.toClassName(expires)+"] for expires"); } setCookie(key, value, exp, secure, path, domain,httpOnly,preserveCase,encode); } @Override public void setCookie(Collection.Key key, Object value, int expires, boolean secure, String path, String domain, boolean httpOnly, boolean preserveCase, boolean encode) throws PageException { _addCookie(key,Caster.toString(value),expires,secure,path,domain,httpOnly,preserveCase,encode); super.set (key, value); } @Override public void setCookieEL(Collection.Key key, Object value, int expires, boolean secure, String path, String domain, boolean httpOnly, boolean preserveCase, boolean encode) { _addCookie(key,Caster.toString(value,""),expires,secure,path,domain,httpOnly,preserveCase,encode); super.setEL (key, value); } private void _addCookie(Key key, String value, int expires, boolean secure, String path, String domain, boolean httpOnly, boolean preserveCase, boolean encode) { String name=preserveCase?key.getString():key.getUpperString(); // build the value StringBuilder sb=new StringBuilder(); /*Name*/ sb.append(enc(name)).append('=').append(enc(value)); /*Path*/sb.append(";Path=").append(enc(path)); /*Domain*/if(!StringUtil.isEmpty(domain))sb.append(";Domain=").append(enc(domain)); /*Expires*/if(expires!=EXPIRES_NULL)sb.append(";Expires=").append(DateTimeUtil.toHTTPTimeString(System.currentTimeMillis()+(expires*1000L),false)); /*Secure*/if(secure)sb.append(";Secure"); /*HTTPOnly*/if(httpOnly)sb.append(";HTTPOnly"); rsp.addHeader("Set-Cookie", sb.toString()); } /*private void _addCookieOld(Key key, String value, int expires, boolean secure, String path, String domain, boolean httpOnly, boolean preserveCase, boolean encode) { String name=preserveCase?key.getString():key.getUpperString(); if(encode) { name=enc(name); value=enc(value); } javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name,value); cookie.setMaxAge(expires); cookie.setSecure(secure); cookie.setPath(path); if(!StringUtil.isEmpty(domain,true))cookie.setDomain(domain); if(httpOnly) setHTTPOnly(cookie); rsp.addCookie(cookie); }*/ private int toExpires(String expires) throws ExpressionException { String str=StringUtil.toLowerCase(expires.toString()); if(str.equals("now"))return 0; else if(str.equals("never"))return NEVER; else { DateTime dt = DateCaster.toDateAdvanced(expires,false,null,null); if(dt!=null) { return toExpires(dt); } return toExpires(Caster.toIntValue(expires)); } } private int toExpires(Number expires) { return toExpires(expires.intValue()); } private int toExpires(int expires) { return expires*24*60*60; } private int toExpires(Date expires) { double diff = expires.getTime()-System.currentTimeMillis(); return (int)Math.round(diff/1000D); } private int toExpires(TimeSpan span) { return (int)span.getSeconds(); } @Override public void initialize(PageContext pc) { Config config = ThreadLocalPageContext.getConfig(pc); charset = pc.getConfig().getWebCharset(); if(scriptProtected==ScriptProtected.UNDEFINED) { scriptProtected=((pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_COOKIE)>0)? ScriptProtected.YES:ScriptProtected.NO; } super.initialize(pc); HttpServletRequest req = pc. getHttpServletRequest(); this.rsp=pc. getHttpServletResponse(); javax.servlet.http.Cookie[] cookies=ReqRspUtil.getCookies(config,req); try { for(int i=0;i<cookies.length;i++) { set(config,cookies[i]); } } catch (Exception e) {} } @Override public void release() { raw.clear(); scriptProtected=ScriptProtected.UNDEFINED; super.release(); } @Override public void release(PageContext pc) { raw.clear(); scriptProtected=ScriptProtected.UNDEFINED; super.release(pc); } @Override public boolean isScriptProtected() { return scriptProtected==ScriptProtected.YES; } @Override public void setScriptProtecting(ApplicationContext ac,boolean scriptProtected) { int _scriptProtected = scriptProtected?ScriptProtected.YES:ScriptProtected.NO; if(isInitalized() && _scriptProtected!=this.scriptProtected) { Iterator<Entry<String, String>> it = raw.entrySet().iterator(); Entry<String, String> entry; String key,value; while(it.hasNext()){ entry = it.next(); key=entry.getKey().toString(); value=dec(entry.getValue().toString()); super.setEL(KeyImpl.init(key), scriptProtected?ScriptProtect.translate(value):value); } } this.scriptProtected=_scriptProtected; } public String dec(String str) { return ReqRspUtil.decode(str,charset,false); } public String enc(String str) { if(ReqRspUtil.needEncoding(str, true)) return ReqRspUtil.encode(str,charset); return str; } @Override public void resetEnv(PageContext pc) { } @Override public void touchBeforeRequest(PageContext pc) { } @Override public void touchAfterRequest(PageContext pc) { } public static void setHTTPOnly(javax.servlet.http.Cookie cookie) { try { if(setHttpOnly==null) { setHttpOnly=cookie.getClass().getMethod("setHttpOnly", SET_HTTP_ONLY_ARGS_CLASSES); } setHttpOnly.invoke(cookie, SET_HTTP_ONLY_ARGS); } catch (Throwable t) { // HTTPOnly is not supported in this enviroment } } public static boolean isHTTPOnly(javax.servlet.http.Cookie cookie) { try { if(isHttpOnly==null) { isHttpOnly=cookie.getClass().getMethod("isHttpOnly", IS_HTTP_ONLY_ARGS_CLASSES); } return Caster.toBooleanValue(isHttpOnly.invoke(cookie, IS_HTTP_ONLY_ARGS)); } catch (Throwable t) { return false; } } }