001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.type.scope; 020 021import java.lang.reflect.Method; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpServletResponse; 029 030import lucee.commons.date.DateTimeUtil; 031import lucee.commons.lang.ExceptionUtil; 032import lucee.commons.lang.StringUtil; 033import lucee.runtime.PageContext; 034import lucee.runtime.PageContextImpl; 035import lucee.runtime.config.Config; 036import lucee.runtime.engine.ThreadLocalPageContext; 037import lucee.runtime.exp.ExpressionException; 038import lucee.runtime.exp.PageException; 039import lucee.runtime.listener.ApplicationContext; 040import lucee.runtime.net.http.ReqRspUtil; 041import lucee.runtime.op.Caster; 042import lucee.runtime.op.Decision; 043import lucee.runtime.op.date.DateCaster; 044import lucee.runtime.security.ScriptProtect; 045import lucee.runtime.type.Collection; 046import lucee.runtime.type.KeyImpl; 047import lucee.runtime.type.Struct; 048import lucee.runtime.type.dt.DateTime; 049import lucee.runtime.type.dt.TimeSpan; 050import lucee.runtime.type.util.KeyConstants; 051 052/** 053 * Implementation of the Cookie scope 054 */ 055public final class CookieImpl extends ScopeSupport implements Cookie,ScriptProtected { 056 057 058 private static final long serialVersionUID = -2341079090783313736L; 059 060 public static final int NEVER = 946626690; 061 062 private HttpServletResponse rsp; 063 private int scriptProtected=ScriptProtected.UNDEFINED; 064 private Map<String,String> raw=new HashMap<String,String>(); 065 private String charset; 066 067 068 private static final Class<?>[] IS_HTTP_ONLY_ARGS_CLASSES = new Class[]{}; 069 private static final Object[] IS_HTTP_ONLY_ARGS = new Object[]{}; 070 071 private static final Class<?>[] SET_HTTP_ONLY_ARGS_CLASSES = new Class[]{boolean.class}; 072 private static final Object[] SET_HTTP_ONLY_ARGS = new Object[]{Boolean.TRUE}; 073 074 private static final int EXPIRES_NULL = -1; 075 private static Method isHttpOnly; 076 private static Method setHttpOnly; 077 078 /** 079 * constructor for the Cookie Scope 080 */ 081 public CookieImpl() { 082 super(false,"cookie",SCOPE_COOKIE); 083 } 084 085 086 @Override 087 public Object setEL(Collection.Key key, Object value) { 088 try { 089 return set(key,value); 090 } catch (PageException e) { 091 return null; 092 } 093 } 094 095 public Object set(Collection.Key key, Object value) throws PageException { 096 raw.remove(key.getLowerString()); 097 098 if(Decision.isStruct(value)) { 099 Struct sct = Caster.toStruct(value); 100 Object expires=sct.get(KeyConstants._expires,null); 101 Object val=sct.get(KeyConstants._value,null); 102 boolean secure=Caster.toBooleanValue(sct.get(KeyConstants._secure,null),false); 103 boolean httpOnly=Caster.toBooleanValue(sct.get(KeyConstants._httponly,null),false); 104 String domain=Caster.toString(sct.get(KeyConstants._domain,null),null); 105 String path=Caster.toString(sct.get(KeyConstants._path,null),null); 106 boolean preserveCase=Caster.toBooleanValue(sct.get(KeyConstants._preservecase,null),false); 107 Boolean encode=Caster.toBoolean(sct.get(KeyConstants._encode,null),null); 108 if(encode==null)encode=Caster.toBoolean(sct.get(KeyConstants._encodevalue,Boolean.TRUE),Boolean.TRUE); 109 110 setCookie(key, val, expires, secure, path, domain,httpOnly,preserveCase,encode.booleanValue()); 111 } 112 else setCookie(key,value,null,false,"/",null,false,false,true); 113 return value; 114 } 115 116 private void set(Config config,javax.servlet.http.Cookie cookie) throws PageException { 117 118 String name=StringUtil.toLowerCase(ReqRspUtil.decode(cookie.getName(),charset,false)); 119 if (!raw.containsKey(name) || !StringUtil.isEmpty(cookie.getPath())) { 120 // when there are multiple cookies with the same name let the cookies with a path overwrite a cookie without a path. 121 raw.put(name,cookie.getValue()); 122 if(isScriptProtected()) super.set (KeyImpl.init(name),ScriptProtect.translate(dec(cookie.getValue()))); 123 else super.set (KeyImpl.init(name),dec(cookie.getValue())); 124 } 125 } 126 127 @Override 128 public void clear() { 129 raw.clear(); 130 Collection.Key[] keys = keys(); 131 for(int i=0;i<keys.length;i++) { 132 removeEL(keys[i],false); 133 } 134 } 135 136 @Override 137 138 public Object remove(Collection.Key key) throws PageException { 139 raw.remove(key.getLowerString()); 140 return remove(key, true); 141 } 142 143 public Object remove(Collection.Key key, boolean alsoInResponse) throws PageException { 144 raw.remove(key.getLowerString()); 145 Object obj=super.remove (key); 146 if(alsoInResponse)removeCookie(key); 147 return obj; 148 } 149 150 public Object removeEL(Collection.Key key) { 151 return removeEL(key, true); 152 } 153 154 private Object removeEL(Collection.Key key, boolean alsoInResponse) { 155 raw.remove(key.getLowerString()); 156 Object obj=super.removeEL (key); 157 if(obj!=null && alsoInResponse) removeCookie(key); 158 return obj; 159 } 160 161 private void removeCookie(Collection.Key key) { 162 ReqRspUtil.removeCookie(rsp, key.getUpperString()); 163 } 164 165 @Override 166 public void setCookie(Collection.Key key, Object value, Object expires, boolean secure, String path, String domain) throws PageException { 167 setCookie(key, value, expires, secure, path, domain, false, false, true); 168 } 169 170 @Override 171 public void setCookie(Collection.Key key, Object value, int expires, boolean secure, String path, String domain) throws PageException { 172 setCookie(key, value, expires, secure, path, domain, false, false, true); 173 } 174 175 @Override 176 public void setCookieEL(Collection.Key key, Object value, int expires, boolean secure, String path, String domain) { 177 setCookieEL(key, value, expires, secure, path, domain, false, false, true); 178 } 179 180 @Override 181 public void setCookie(Collection.Key key, Object value, Object expires, boolean secure, String path, String domain, 182 boolean httpOnly, boolean preserveCase, boolean encode) throws PageException { 183 int exp=EXPIRES_NULL; 184 185 // expires 186 if(expires==null) { 187 exp=EXPIRES_NULL; 188 } 189 else if(expires instanceof Date) { 190 exp=toExpires((Date)expires); 191 } 192 else if(expires instanceof TimeSpan) { 193 exp=toExpires((TimeSpan)expires); 194 } 195 else if(expires instanceof Number) { 196 exp=toExpires((Number)expires); 197 } 198 else if(expires instanceof String) { 199 exp=toExpires((String)expires); 200 201 } 202 else { 203 throw new ExpressionException("invalid type ["+Caster.toClassName(expires)+"] for expires"); 204 } 205 206 setCookie(key, value, exp, secure, path, domain,httpOnly,preserveCase,encode); 207 } 208 209 @Override 210 public void setCookie(Collection.Key key, Object value, int expires, boolean secure, String path, String domain, 211 boolean httpOnly, boolean preserveCase, boolean encode) throws PageException { 212 213 _addCookie(key,Caster.toString(value),expires,secure,path,domain,httpOnly,preserveCase,encode); 214 super.set (key, value); 215 } 216 217 218 @Override 219 public void setCookieEL(Collection.Key key, Object value, int expires, boolean secure, String path, String domain, 220 boolean httpOnly, boolean preserveCase, boolean encode) { 221 222 _addCookie(key,Caster.toString(value,""),expires,secure,path,domain,httpOnly,preserveCase,encode); 223 super.setEL (key, value); 224 } 225 226 private void _addCookie(Key key, String value, int expires, boolean secure, String path, String domain, 227 boolean httpOnly, boolean preserveCase, boolean encode) { 228 String name=preserveCase?key.getString():key.getUpperString(); 229 230 // build the value 231 StringBuilder sb=new StringBuilder(); 232 /*Name*/ sb.append(enc(name)).append('=').append(enc(value)); 233 /*Path*/sb.append(";Path=").append(enc(path)); 234 /*Domain*/if(!StringUtil.isEmpty(domain))sb.append(";Domain=").append(enc(domain)); 235 /*Expires*/if(expires!=EXPIRES_NULL)sb.append(";Expires=").append(DateTimeUtil.toHTTPTimeString(System.currentTimeMillis()+(expires*1000L),false)); 236 /*Secure*/if(secure)sb.append(";Secure"); 237 /*HTTPOnly*/if(httpOnly)sb.append(";HTTPOnly"); 238 239 rsp.addHeader("Set-Cookie", sb.toString()); 240 241 } 242 243 /*private void _addCookieOld(Key key, String value, int expires, boolean secure, String path, String domain, 244 boolean httpOnly, boolean preserveCase, boolean encode) { 245 String name=preserveCase?key.getString():key.getUpperString(); 246 if(encode) { 247 name=enc(name); 248 value=enc(value); 249 } 250 251 javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name,value); 252 cookie.setMaxAge(expires); 253 cookie.setSecure(secure); 254 cookie.setPath(path); 255 if(!StringUtil.isEmpty(domain,true))cookie.setDomain(domain); 256 if(httpOnly) setHTTPOnly(cookie); 257 rsp.addCookie(cookie); 258 259 }*/ 260 261 262 private int toExpires(String expires) throws ExpressionException { 263 String str=StringUtil.toLowerCase(expires.toString()); 264 if(str.equals("now"))return 0; 265 else if(str.equals("never"))return NEVER; 266 else { 267 DateTime dt = DateCaster.toDateAdvanced(expires,DateCaster.CONVERTING_TYPE_NONE,null,null); 268 if(dt!=null) { 269 return toExpires(dt); 270 } 271 return toExpires(Caster.toIntValue(expires)); 272 } 273 } 274 275 276 private int toExpires(Number expires) { 277 return toExpires(expires.intValue()); 278 } 279 private int toExpires(int expires) { 280 return expires*24*60*60; 281 } 282 283 284 private int toExpires(Date expires) { 285 double diff = expires.getTime()-System.currentTimeMillis(); 286 return (int)Math.round(diff/1000D); 287 } 288 private int toExpires(TimeSpan span) { 289 return (int)span.getSeconds(); 290 } 291 292 293 294 295 296 297 @Override 298 public void initialize(PageContext pc) { 299 Config config = ThreadLocalPageContext.getConfig(pc); 300 charset = ((PageContextImpl)pc).getWebCharset().name(); 301 if(scriptProtected==ScriptProtected.UNDEFINED) { 302 scriptProtected=((pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_COOKIE)>0)? 303 ScriptProtected.YES:ScriptProtected.NO; 304 } 305 super.initialize(pc); 306 307 HttpServletRequest req = pc. getHttpServletRequest(); 308 this.rsp=pc. getHttpServletResponse(); 309 javax.servlet.http.Cookie[] cookies=ReqRspUtil.getCookies(req,((PageContextImpl)pc).getWebCharset()); 310 try { 311 for(int i=0;i<cookies.length;i++) { 312 set(config,cookies[i]); 313 } 314 } 315 catch (Exception e) {} 316 } 317 318 @Override 319 public void release() { 320 raw.clear(); 321 scriptProtected=ScriptProtected.UNDEFINED; 322 super.release(); 323 } 324 325 @Override 326 public void release(PageContext pc) { 327 raw.clear(); 328 scriptProtected=ScriptProtected.UNDEFINED; 329 super.release(pc); 330 } 331 332 333 334 @Override 335 public boolean isScriptProtected() { 336 return scriptProtected==ScriptProtected.YES; 337 } 338 339 @Override 340 public void setScriptProtecting(ApplicationContext ac,boolean scriptProtected) { 341 int _scriptProtected = scriptProtected?ScriptProtected.YES:ScriptProtected.NO; 342 if(isInitalized() && _scriptProtected!=this.scriptProtected) { 343 Iterator<Entry<String, String>> it = raw.entrySet().iterator(); 344 Entry<String, String> entry; 345 String key,value; 346 347 while(it.hasNext()){ 348 entry = it.next(); 349 key=entry.getKey().toString(); 350 value=dec(entry.getValue().toString()); 351 super.setEL(KeyImpl.init(key), scriptProtected?ScriptProtect.translate(value):value); 352 } 353 } 354 this.scriptProtected=_scriptProtected; 355 } 356 357 358 public String dec(String str) { 359 return ReqRspUtil.decode(str,charset,false); 360 } 361 public String enc(String str) { 362 if(ReqRspUtil.needEncoding(str, false)) 363 return ReqRspUtil.encode(str,charset); 364 return str; 365 } 366 367 368 @Override 369 public void resetEnv(PageContext pc) { 370 } 371 372 373 @Override 374 public void touchBeforeRequest(PageContext pc) { 375 } 376 377 378 @Override 379 public void touchAfterRequest(PageContext pc) { 380 } 381 382 public static void setHTTPOnly(javax.servlet.http.Cookie cookie) { 383 try { 384 if(setHttpOnly==null) { 385 setHttpOnly=cookie.getClass().getMethod("setHttpOnly", SET_HTTP_ONLY_ARGS_CLASSES); 386 } 387 setHttpOnly.invoke(cookie, SET_HTTP_ONLY_ARGS); 388 } 389 catch (Throwable t) { 390 ExceptionUtil.rethrowIfNecessary(t); 391 // HTTPOnly is not supported in this enviroment 392 } 393 } 394 395 public static boolean isHTTPOnly(javax.servlet.http.Cookie cookie) { 396 try { 397 if(isHttpOnly==null) { 398 isHttpOnly=cookie.getClass().getMethod("isHttpOnly", IS_HTTP_ONLY_ARGS_CLASSES); 399 } 400 return Caster.toBooleanValue(isHttpOnly.invoke(cookie, IS_HTTP_ONLY_ARGS)); 401 } 402 catch (Throwable t) { 403 ExceptionUtil.rethrowIfNecessary(t); 404 return false; 405 } 406 } 407}