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.interpreter;
020
021import lucee.commons.lang.ParserString;
022import lucee.commons.lang.StringList;
023import lucee.commons.lang.StringUtil;
024import lucee.runtime.PageContext;
025import lucee.runtime.PageContextImpl;
026import lucee.runtime.config.NullSupportHelper;
027import lucee.runtime.exp.PageException;
028import lucee.runtime.op.Caster;
029import lucee.runtime.type.Collection;
030import lucee.runtime.type.KeyImpl;
031import lucee.runtime.type.ref.VariableReference;
032import lucee.runtime.type.scope.Argument;
033import lucee.runtime.type.scope.CallerImpl;
034import lucee.runtime.type.scope.Local;
035import lucee.runtime.type.scope.Scope;
036import lucee.runtime.type.scope.ScopeSupport;
037import lucee.runtime.type.scope.Undefined;
038import lucee.runtime.type.scope.UndefinedImpl;
039import lucee.runtime.type.scope.Variables;
040import lucee.runtime.type.util.KeyConstants;
041import lucee.runtime.type.util.ListUtil;
042/**
043 * Class to check and interpret Variable Strings
044 */
045public final class VariableInterpreter {
046    
047        private static final Object NULL = new Object();
048
049        /**
050         * reads a subelement from a struct
051         * @param pc
052         * @param collection
053         * @param var
054         * @return matching Object
055         * @throws PageException
056         */
057        public static Object getVariable(PageContext pc, Collection collection,String var) throws PageException {                       
058            StringList list = parse(pc,new ParserString(var),false);
059        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
060        
061        while(list.hasNextNext()) {
062            collection=Caster.toCollection(collection.get(KeyImpl.init(list.next())));
063        }
064        return collection.get(KeyImpl.init(list.next()));
065        }
066        
067        public static String scopeInt2String(int type) {
068                switch(type) {
069                        case Scope.SCOPE_APPLICATION:   return "application";
070                        case Scope.SCOPE_ARGUMENTS:             return "arguments";
071                        case Scope.SCOPE_CGI:                   return "cgi";
072                        case Scope.SCOPE_COOKIE:                return "cookie";
073                        case Scope.SCOPE_CLIENT:                return "client";
074                        case Scope.SCOPE_FORM:                  return "form";
075                        case Scope.SCOPE_REQUEST:               return "request";
076                        case Scope.SCOPE_SESSION:               return "session";
077                        case Scope.SCOPE_SERVER:                return "server";
078                        case Scope.SCOPE_URL:                   return "url";
079                        case Scope.SCOPE_VARIABLES:             return "variables";
080                        case Scope.SCOPE_CLUSTER:               return "cluster";
081                        case Scope.SCOPE_LOCAL:                 return "local";
082                }
083                return null;
084        }
085        
086
087        public static Object getVariableEL(PageContext pc, Collection collection,String var) {                  
088            StringList list = parse(pc,new ParserString(var),false);
089        if(list==null) return null;
090       
091        while(list.hasNextNext()) {
092                collection=Caster.toCollection(collection.get(list.next(),null),null);
093                if(collection==null) return null;
094        }
095        return collection.get(list.next(),null);
096        }
097    
098    /**
099         * get a variable from page context
100         * @param pc Page Context
101         * @param var variable string to get value to
102         * @return the value
103     * @throws PageException
104         */
105        public static Object getVariable(PageContext pc,String var) throws PageException {
106        StringList list = parse(pc,new ParserString(var),false);
107        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
108        
109                int scope=scopeString2Int(list.next());
110                Object coll =null; 
111                if(scope==Scope.SCOPE_UNDEFINED) {
112                    coll=pc.undefinedScope().get(list.current());
113                }
114                else {
115                        coll=VariableInterpreter.scope(pc, scope, list.hasNext());
116                }
117                
118                while(list.hasNext()) {
119                        coll=pc.getVariableUtil().get(pc,coll,list.next());
120                }
121                return coll;
122    }
123        
124
125
126        public static Object getVariableAsCollection(PageContext pc,String var) throws PageException {
127        StringList list = parse(pc,new ParserString(var),false);
128        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
129        
130                int scope=scopeString2Int(list.next());
131                Object coll =null; 
132                if(scope==Scope.SCOPE_UNDEFINED) {
133                    coll=pc.undefinedScope().getCollection(list.current());
134                }
135                else {
136                    coll=VariableInterpreter.scope(pc, scope, list.hasNext());
137                }
138                
139                while(list.hasNext()) {
140                        coll=pc.getVariableUtil().getCollection(pc,coll,list.next());
141                }
142                return coll;
143    }
144        
145
146        public static Object getVariable(PageContext pc,String str,Scope scope) throws PageException {
147                return _variable(pc, str,NULL, scope);
148        }
149        public static Object setVariable(PageContext pc,String str,Object value,Scope scope) throws PageException {
150                return _variable(pc, str,value, scope);
151        }
152        
153        public static Object _variable(PageContext pc,String str,Object value,Scope scope) throws PageException {
154                // define a ohter enviroment for the function
155                if(scope!=null){
156                        
157                        // Variables Scope
158                        Variables var=null;
159                        if(scope instanceof Variables){
160                                var=(Variables) scope;
161                        }
162                        else if(scope instanceof CallerImpl){
163                                var=((CallerImpl) scope).getVariablesScope();
164                        }
165                        if(var!=null){
166                                Variables current=pc.variablesScope();
167                                pc.setVariablesScope(var);
168                        try{
169                                if(value!=NULL) return setVariable(pc, str,value);
170                                return getVariable(pc, str);
171                        }
172                        finally{
173                                pc.setVariablesScope(current);
174                        }
175                        }
176                        
177                        // Undefined Scope
178                        else if(scope instanceof Undefined) {
179                                PageContextImpl pci=(PageContextImpl) pc;
180                                Undefined undefined=(Undefined) scope;
181                                
182                                boolean check=undefined.getCheckArguments();
183                                Variables orgVar=pc.variablesScope();
184                                Argument orgArgs=pc.argumentsScope();
185                        Local orgLocal=pc.localScope();
186                                
187                                pci.setVariablesScope(undefined.variablesScope());
188                                if(check)pci.setFunctionScopes(undefined.localScope(), undefined.argumentsScope());
189                        try{
190                                if(value!=NULL) return setVariable(pc, str,value);
191                                return getVariable(pc, str);
192                        }
193                        finally{
194                                pc.setVariablesScope(orgVar);
195                                if(check)pci.setFunctionScopes(orgLocal,orgArgs);
196                        }
197                        }
198                }
199                if(value!=NULL) return setVariable(pc, str,value);
200                return getVariable(pc, str);
201        }
202        
203        /**
204         * get a variable from page context
205         * @param pc Page Context
206         * @param var variable string to get value to
207         * @param defaultValue value returnded if variable was not found
208         * @return the value or default value if not found
209         */
210        public static Object getVariableEL(PageContext pc,String var, Object defaultValue) {
211        StringList list = parse(pc,new ParserString(var),false);
212        if(list==null) return defaultValue;
213        
214                int scope=scopeString2Int(list.next());
215                Object coll =null; 
216                if(scope==Scope.SCOPE_UNDEFINED) {
217                    coll=pc.undefinedScope().get(KeyImpl.init(list.current()),NullSupportHelper.NULL());
218                    if(coll==NullSupportHelper.NULL()) return defaultValue;
219                }
220                else {
221                    try {
222                coll=VariableInterpreter.scope(pc, scope, list.hasNext());
223                        //coll=pc.scope(scope);
224            } 
225                    catch (PageException e) {
226                return defaultValue;
227            }
228                }
229                
230                while(list.hasNext()) {
231                        coll=pc.getVariableUtil().get(pc,coll,KeyImpl.init(list.next()),NullSupportHelper.NULL());
232                        if(coll==NullSupportHelper.NULL()) return defaultValue;
233                }
234                return coll;
235    }
236        
237        public static Object getVariableELAsCollection(PageContext pc,String var, Object defaultValue) {
238        StringList list = parse(pc,new ParserString(var),false);
239        if(list==null) return defaultValue;
240        
241                int scope=scopeString2Int(list.next());
242                Object coll =null; 
243                if(scope==Scope.SCOPE_UNDEFINED) {
244                    try {
245                                coll=pc.undefinedScope().getCollection(list.current());
246                        } 
247                    catch (PageException e) {
248                                coll=null;
249                        }
250                    if(coll==null) return defaultValue;
251                }
252                else {
253                    try {
254                coll=VariableInterpreter.scope(pc, scope, list.hasNext());
255                        //coll=pc.scope(scope);
256            } 
257                    catch (PageException e) {
258                return defaultValue;
259            }
260                }
261                
262                while(list.hasNext()) {
263                        coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
264                        if(coll==null) return defaultValue;
265                }
266                return coll;
267    }
268        /**
269         * return a variable reference by string syntax ("scopename.key.key" -> "url.name")
270         * a variable reference, references to variable, to modifed it, with global effect.
271         * @param pc
272         * @param var variable name to get
273         * @return variable as Reference
274         * @throws PageException
275         */
276        public static VariableReference getVariableReference(PageContext pc,String var) throws PageException { 
277            StringList list = parse(pc,new ParserString(var),false);
278        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
279        
280                if(list.size()==1) {
281                        return new VariableReference(pc.undefinedScope(),list.next()); 
282                }
283                int scope=scopeString2Int(list.next());
284                
285                Object coll;
286                if(scope==Scope.SCOPE_UNDEFINED){
287                        coll=pc.touch(pc.undefinedScope(),list.current());
288                }
289                else{
290                        coll=VariableInterpreter.scope(pc, scope, list.hasNext());
291                        //coll=pc.scope(scope);
292                }
293                
294                
295                while(list.hasNextNext()) {
296                        coll=pc.touch(coll,list.next());
297                }
298
299                if(!(coll instanceof Collection))
300                        throw new InterpreterException("invalid variable ["+var+"]");
301                return new VariableReference((Collection)coll,list.next());
302        }
303        
304        public static VariableReference getVariableReference(PageContext pc,Collection.Key[] keys, boolean keepScope) throws PageException { 
305           
306                if(keys.length==1) {
307                        if(keepScope) {
308                                Collection coll = ((UndefinedImpl)pc.undefinedScope()).getScopeFor(keys[0],null);
309                                if(coll!=null) return new VariableReference(coll,keys[0]); 
310                        }
311                        return new VariableReference(pc.undefinedScope(),keys[0]); 
312                }
313                int scope=scopeKey2Int(keys[0]);
314                
315                Object coll;
316                
317                if(scope==Scope.SCOPE_UNDEFINED){
318                        coll=pc.touch(pc.undefinedScope(),keys[0]);
319                }
320                else{
321                        coll=VariableInterpreter.scope(pc, scope, keys.length>1);
322                }
323                
324                for(int i=1;i<(keys.length-1);i++){
325                        coll=pc.touch(coll,keys[i]);
326                }
327                
328
329                if(!(coll instanceof Collection))
330                        throw new InterpreterException("invalid variable ["+ListUtil.arrayToList(keys, ".")+"]");
331                return new VariableReference((Collection)coll,keys[keys.length-1]);
332        }
333        
334        public static VariableReference getVariableReference(PageContext pc,Collection.Key key, boolean keepScope) {   
335                if(keepScope) {
336                        Collection coll = ((UndefinedImpl)pc.undefinedScope()).getScopeFor(key,null);
337                        if(coll!=null) return new VariableReference(coll,key); 
338                }
339                return new VariableReference(pc.undefinedScope(),key); 
340        } 
341        
342        /**
343         * sets a variable to page Context
344         * @param pc pagecontext of the new variable
345         * @param var String of variable definition
346         * @param value value to set to variable
347         * @return value setted
348         * @throws PageException
349         */
350        public static Object setVariable(PageContext pc,String var, Object value) throws PageException {                        
351            StringList list = parse(pc,new ParserString(var),false);
352        if(list==null) throw new InterpreterException("invalid variable name declaration ["+var+"]");
353
354                if(list.size()==1) {
355                        return pc.undefinedScope().set(list.next(),value);
356                }
357                
358                // min 2 elements
359                int scope=scopeString2Int(list.next());
360                Object coll;
361                if(scope==Scope.SCOPE_UNDEFINED){
362                        coll=pc.touch(pc.undefinedScope(),list.current());
363                }
364                else {
365                        coll=VariableInterpreter.scope(pc, scope, true);
366                        //coll=pc.scope(scope);
367                }
368                
369                
370                while(list.hasNextNext()) {
371                    coll=pc.touch(coll,list.next());
372                }
373                return pc.set(coll,list.next(),value);
374        }
375        
376        /**
377         * removes a variable eith matching name from page context
378         * @param pc
379         * @param var
380         * @return has removed or not
381         * @throws PageException
382         */
383        public static Object removeVariable(PageContext pc,String var) throws PageException {   
384            //print.ln("var:"+var);
385            StringList list = parse(pc,new ParserString(var),false);
386        if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
387        
388                if(list.size()==1) {
389                        return pc.undefinedScope().remove(KeyImpl.init(list.next()));
390                }
391        
392                int scope=scopeString2Int(list.next());
393                
394                Object coll;
395                if(scope==Scope.SCOPE_UNDEFINED){
396                        coll=pc.undefinedScope().get(list.current());
397                }
398                else {
399                        coll=VariableInterpreter.scope(pc, scope, true);
400                        //coll=pc.scope(scope);
401                }
402                
403                while(list.hasNextNext()) {
404                    coll=pc.get(coll,list.next());
405                }
406                return Caster.toCollection(coll).remove(KeyImpl.init(list.next()));
407        }
408
409        
410
411        /**
412         * check if a variable is defined in Page Context
413         * @param pc PageContext to check
414         * @param var variable String
415         * @return exists or not
416         */
417        public static boolean isDefined(PageContext pc,String var) {
418                StringList list = parse(pc,new ParserString(var),false);
419                if(list==null) return false;
420        try {
421                        int scope=scopeString2Int(list.next());
422                        Object coll =NULL; 
423                        if(scope==Scope.SCOPE_UNDEFINED) {
424                                coll=pc.undefinedScope().get(list.current(),null);
425                                if(coll==null)return false;
426                        }
427                        else {
428                                coll=VariableInterpreter.scope(pc, scope, list.hasNext());
429                                //coll=pc.scope(scope);
430                        }
431                        
432                        while(list.hasNext()) {
433                                coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
434                                if(coll==null)return false;
435                        }
436                } catch (PageException e) {
437                return false;
438            }
439                return true;
440    }
441                
442
443        
444        
445        /*
446        public static boolean isDefined(PageContext pc,String var) {
447        StringList list = parse(pc,new ParserString(var));
448        if(list==null) return false;
449        
450                int scope=scopeString2Int(list.next());
451                Object coll =NULL; 
452                if(scope==Scope.SCOPE_UNDEFINED) {
453                    coll=pc.undefinedScope().get(list.current(),NULL);
454                    if(coll==NULL) return false;
455                }
456                else {
457                    try {
458                coll=pc.scope(scope);
459            } catch (PageException e) {
460                return false;
461            }
462                }
463                
464                while(list.hasNext()) {
465                        coll=pc.getVariableUtil().get(pc,coll,list.next(),NULL);
466                        //print.out(coll);
467                        if(coll==NULL) return false;
468                }
469       
470                return true;
471    }
472         */
473        
474    
475    /**
476     * parse a Literal variable String and return result as String List
477     * @param pc Page Context
478     * @param ps ParserString to read
479     * @return Variable Definition in a String List
480     */
481    private static StringList parse(PageContext pc,ParserString ps, boolean doLowerCase) {
482        String id=readIdentifier(ps,doLowerCase);
483        if(id==null)return null;
484        StringList list=new StringList(id);
485        CFMLExpressionInterpreter interpreter=null;
486        
487        while(true) {
488            if(ps.forwardIfCurrent('.')) {
489                    id=readIdentifier(ps,doLowerCase);
490                    if(id==null)return null;
491                    list.add(id);
492            }
493            else if(ps.forwardIfCurrent('[')) {
494                if(interpreter==null)interpreter=new CFMLExpressionInterpreter(false);
495                try {
496                    list.add(Caster.toString(interpreter.interpretPart(pc,ps)));
497                } catch (PageException e) {
498                    return null;
499                }
500                if(!ps.forwardIfCurrent(']')) return null;
501                ps.removeSpace();
502            }
503            else break;
504        }
505        if(ps.isValidIndex()) return null;
506        list.reset();
507        return list;
508    }
509    
510    public static StringList parse(String var, boolean doLowerCase) {
511        ParserString ps = new ParserString(var);
512        String id=readIdentifier(ps,doLowerCase);
513        if(id==null)return null;
514        StringList list=new StringList(id);
515        
516        while(true) {
517            if(ps.forwardIfCurrent('.')) {
518                    id=readIdentifier(ps,doLowerCase);
519                    if(id==null)return null;
520                    list.add(id);
521            }
522            else break;
523        }
524        if(ps.isValidIndex()) return null;
525        list.reset();
526        return list;
527    }
528
529        /**
530         * translate a string type definition to its int representation
531         * @param type type to translate
532         * @return int representation matching to given string
533         */
534        public static int scopeString2Int(String type) {
535                type=StringUtil.toLowerCase(type);
536                char c=type.charAt(0);
537                if('a'==c) {
538                        if("application".equals(type))          return Scope.SCOPE_APPLICATION;
539                        else if("arguments".equals(type))       return Scope.SCOPE_ARGUMENTS;
540                }
541                else if('c'==c) {
542                        if("cgi".equals(type))                          return Scope.SCOPE_CGI;
543                        if("cookie".equals(type))                       return Scope.SCOPE_COOKIE;
544                        if("client".equals(type))                       return Scope.SCOPE_CLIENT;
545                        if("cluster".equals(type))                      return Scope.SCOPE_CLUSTER;
546                }
547                else if('f'==c) {
548                        if("form".equals(type))                         return Scope.SCOPE_FORM;
549                }
550                else if('l'==c) {
551                        if("local".equals(type))                                return Scope.SCOPE_LOCAL;// LLL
552                }
553                else if('r'==c) {
554                        if("request".equals(type))                      return Scope.SCOPE_REQUEST;
555                }
556                else if('s'==c) {
557                        if("session".equals(type))                      return Scope.SCOPE_SESSION;
558                        if("server".equals(type))                       return Scope.SCOPE_SERVER;
559                }
560                else if('u'==c) {
561                        if("url".equals(type))                          return Scope.SCOPE_URL;
562                }
563                else if('v'==c) {
564                        if("variables".equals(type))            return Scope.SCOPE_VARIABLES;
565                }
566                return Scope.SCOPE_UNDEFINED;
567        }
568        
569        public static int scopeKey2Int(Collection.Key type) {
570                char c=type.lowerCharAt(0);
571                if('a'==c) {
572                        if(KeyConstants._application.equalsIgnoreCase(type))            return Scope.SCOPE_APPLICATION;
573                        else if(KeyConstants._arguments.equalsIgnoreCase(type)) return Scope.SCOPE_ARGUMENTS;
574                }
575                else if('c'==c) {
576                        if(KeyConstants._cgi.equalsIgnoreCase(type))                            return Scope.SCOPE_CGI;
577                        if(KeyConstants._cookie.equalsIgnoreCase(type))                 return Scope.SCOPE_COOKIE;
578                        if(KeyConstants._client.equalsIgnoreCase(type))                 return Scope.SCOPE_CLIENT;
579                        if(KeyConstants._cluster.equalsIgnoreCase(type))                        return Scope.SCOPE_CLUSTER; 
580                }
581                else if('f'==c) {
582                        if(KeyConstants._form.equalsIgnoreCase(type))                           return Scope.SCOPE_FORM;
583                }
584                else if('r'==c) {
585                        if(KeyConstants._request.equalsIgnoreCase(type))                        return Scope.SCOPE_REQUEST;
586                }
587                else if('s'==c) {
588                        if(KeyConstants._session.equalsIgnoreCase(type))                        return Scope.SCOPE_SESSION;
589                        if(KeyConstants._server.equalsIgnoreCase(type))                 return Scope.SCOPE_SERVER;
590                }
591                else if('u'==c) {
592                        if(KeyConstants._url.equalsIgnoreCase(type))                            return Scope.SCOPE_URL;
593                }
594                else if('v'==c) {
595                        if(KeyConstants._variables.equalsIgnoreCase(type))              return Scope.SCOPE_VARIABLES;
596                }
597                return Scope.SCOPE_UNDEFINED;
598        }
599    
600    private static String readIdentifier(ParserString ps, boolean doLowerCase) {
601        
602        ps.removeSpace();
603        if(ps.isAfterLast())return null;
604        int start=ps.getPos();
605        if(!isFirstVarLetter(ps.getCurrentLower())) return null;
606        ps.next();
607        
608        while(ps.isValidIndex()) {
609            if(isVarLetter(ps.getCurrentLower()))ps.next();
610            else break;
611        }
612        ps.removeSpace();
613        return doLowerCase?ps.substringLower(start,ps.getPos()-start):ps.substring(start,ps.getPos()-start);
614    }
615
616
617    private static boolean isFirstVarLetter(char c) {
618        return (c>='a' && c<='z') || c=='_' || c=='$';
619    }
620
621    private static boolean isVarLetter(char c) {
622        return (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_' || c=='$';
623    }
624
625        public static Object scope(PageContext pc, int scope, boolean touch) throws PageException {
626                switch(scope) {
627                case Scope.SCOPE_UNDEFINED:     return pc.undefinedScope();
628                case Scope.SCOPE_URL:           return pc.urlScope();
629                case Scope.SCOPE_FORM:          return pc.formScope();
630                case Scope.SCOPE_VARIABLES:     return pc.variablesScope();
631                case Scope.SCOPE_REQUEST:       return pc.requestScope();
632                case Scope.SCOPE_CGI:           return pc.cgiScope();
633                case Scope.SCOPE_APPLICATION:   return pc.applicationScope();
634                case Scope.SCOPE_ARGUMENTS:     return pc.argumentsScope();
635                case Scope.SCOPE_SESSION:       return pc.sessionScope();
636                case Scope.SCOPE_SERVER:        return pc.serverScope();
637                case Scope.SCOPE_COOKIE:        return pc.cookieScope();
638                case Scope.SCOPE_CLIENT:        return pc.clientScope();
639                case ScopeSupport.SCOPE_VAR:            return pc.localScope();
640                case Scope.SCOPE_CLUSTER:               return pc.clusterScope();
641                
642                case Scope.SCOPE_LOCAL:         
643                                if(touch) return ((PageContextImpl)pc).localTouch();
644                                return ((PageContextImpl)pc).localGet();
645            }
646            return pc.variablesScope();
647        }
648    
649}