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}