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.config;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import javax.servlet.ServletConfig;
030import javax.servlet.ServletContext;
031
032import lucee.commons.digest.MD5;
033import lucee.commons.io.IOUtil;
034import lucee.commons.io.SystemUtil;
035import lucee.commons.io.res.Resource;
036import lucee.commons.io.res.util.ResourceUtil;
037import lucee.commons.lang.StringUtil;
038import lucee.runtime.Mapping;
039import lucee.runtime.PageContext;
040import lucee.runtime.cache.tag.CacheHandlerFactoryCollection;
041import lucee.runtime.exp.SecurityException;
042import lucee.runtime.listener.ApplicationListener;
043import lucee.runtime.listener.ClassicAppListener;
044import lucee.runtime.listener.MixedAppListener;
045import lucee.runtime.listener.ModernAppListener;
046import lucee.runtime.listener.NoneAppListener;
047import lucee.runtime.net.http.ReqRspUtil;
048import lucee.runtime.security.SecurityManager;
049import lucee.runtime.type.Collection.Key;
050import lucee.runtime.type.Struct;
051import lucee.runtime.type.util.ArrayUtil;
052
053
054/**
055 * 
056 */
057public final class ConfigWebUtil {
058    
059    /**
060     * touch a file object by the string definition
061     * @param config 
062     * @param directory
063     * @param path
064     * @param type
065     * @return matching file
066     */
067    public static Resource getFile(Config config, Resource directory,String path, short type) {
068        path=replacePlaceholder(path,config);
069        if(!StringUtil.isEmpty(path,true)) {
070            Resource file=getFile(directory.getRealResource(path),type);
071            if(file!=null) return file;
072
073            file=getFile(config.getResource(path),type);
074            
075            if(file!=null) return file;
076        }
077        return null;
078    }
079    
080    /**
081         * generate a file object by the string definition
082     * @param rootDir 
083     * @param strDir 
084     * @param defaultDir 
085     * @param configDir 
086     * @param type 
087     * @param config 
088     * @return file
089         */
090    static Resource getFile(Resource rootDir,String strDir, String defaultDir,Resource configDir, short type, ConfigImpl config)  {
091        strDir=replacePlaceholder(strDir,config);
092        if(!StringUtil.isEmpty(strDir,true)) {
093                Resource res;
094                if(strDir.indexOf("://")!=-1){ // TODO better impl.
095                        res=getFile(config.getResource(strDir),type);
096                        if(res!=null) return res;
097                }
098                res=getFile(rootDir.getRealResource(strDir),type);
099            if(res!=null) return res;
100
101            res=getFile(config.getResource(strDir),type);
102            if(res!=null) return res;
103        }
104        if(defaultDir==null) return null;
105        Resource file=getFile(configDir.getRealResource(defaultDir),type);
106        return file;
107    }
108
109    public static String replacePlaceholder(String str, Config config) {
110        if(StringUtil.isEmpty(str)) return str;
111        
112        if(StringUtil.startsWith(str,'{')){
113            
114            // Config Server
115            if(str.startsWith("{lucee-config") || str.startsWith("{railo-config")) {
116                if(str.startsWith("}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(14)));
117                else if(str.startsWith("-dir}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(18)));
118                else if(str.startsWith("-directory}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(24)));
119            }
120            
121            
122            else if(config!=null && (str.startsWith("{lucee-server") || str.startsWith("{railo-server"))) {
123                Resource dir=config instanceof ConfigWeb?((ConfigWeb)config).getConfigServerDir():config.getConfigDir();
124                //if(config instanceof ConfigServer && cs==null) cs=(ConfigServer) cw;
125                if(dir!=null) {
126                    if(str.startsWith("}",13)) str=checkResult(str,dir.getReal(str.substring(14)));
127                    else if(str.startsWith("-dir}",13)) str=checkResult(str,dir.getReal(str.substring(18)));
128                    else if(str.startsWith("-directory}",13)) str=checkResult(str,dir.getReal(str.substring(24)));
129                }
130            }
131            // Config Web
132            else if(str.startsWith("{lucee-web") || str.startsWith("{railo-web")) {
133                if(str.startsWith("}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(11)));
134                else if(str.startsWith("-dir}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(15)));
135                else if(str.startsWith("-directory}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(21)));
136            }
137            // Web Root
138            else if(str.startsWith("{web-root")) {
139                if(config instanceof ConfigWeb) {
140                    if(str.startsWith("}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(10)));
141                    else if(str.startsWith("-dir}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(14)));
142                    else if(str.startsWith("-directory}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(20)));
143                }
144            }
145            // Temp
146            else if(str.startsWith("{temp")) {
147                if(str.startsWith("}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(6)).toString());
148                else if(str.startsWith("-dir}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(10)).toString());
149                else if(str.startsWith("-directory}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(16)).toString());
150            }
151            else if(config instanceof ServletConfig){
152                Map<String,String> labels=null;
153                // web
154                if(config instanceof ConfigWebImpl){
155                        labels=((ConfigWebImpl)config).getAllLabels();
156                }
157                // server
158                else if(config instanceof ConfigServerImpl){
159                        labels=((ConfigServerImpl)config).getLabels();
160                }
161                if(labels!=null)str=SystemUtil.parsePlaceHolder(str,((ServletConfig)config).getServletContext(),labels);
162            }
163            else str=SystemUtil.parsePlaceHolder(str);
164            
165            if(StringUtil.startsWith(str,'{')){
166                Struct constants = ((ConfigImpl)config).getConstants();
167                Iterator<Entry<Key, Object>> it = constants.entryIterator();
168                Entry<Key, Object> e;
169                while(it.hasNext()) {
170                        e = it.next();
171                        if(StringUtil.startsWithIgnoreCase(str,"{"+e.getKey().getString()+"}")) {
172                                String value=(String) e.getValue();
173                                str=checkResult(str,config.getResource( value)
174                                        .getReal(str.substring(e.getKey().getString().length()+2)));
175                        break;
176                                
177                        }
178                }
179            }
180        }
181        return str;
182    }
183    
184    
185    
186    private static String checkResult(String src, String res) { 
187        boolean srcEndWithSep=StringUtil.endsWith(src, ResourceUtil.FILE_SEPERATOR) || StringUtil.endsWith(src, '/') || StringUtil.endsWith(src, '\\');
188        boolean resEndWithSep=StringUtil.endsWith(res, ResourceUtil.FILE_SEPERATOR) || StringUtil.endsWith(res, '/') || StringUtil.endsWith(res, '\\');
189        if(srcEndWithSep && !resEndWithSep) return res+ResourceUtil.FILE_SEPERATOR;
190        if(!srcEndWithSep && resEndWithSep) return res.substring(0,res.length()-1);
191        
192        return res;
193        }
194
195        /**
196     * get only a existing file, dont create it
197     * @param sc
198     * @param strDir
199     * @param defaultDir
200     * @param configDir
201     * @param type
202     * @param config 
203     * @return existing file
204     */
205    public static Resource getExistingResource(ServletContext sc,String strDir, String defaultDir,Resource configDir, short type, Config config) {
206        //ARP
207        
208        strDir=replacePlaceholder(strDir,config);
209        if(strDir!=null && strDir.trim().length()>0) {
210                Resource res=sc==null?null:_getExistingFile(config.getResource(ResourceUtil.merge(ReqRspUtil.getRootPath(sc),strDir)),type);
211            if(res!=null) return res;
212            
213            res=_getExistingFile(config.getResource(strDir),type);
214            if(res!=null) return res;
215        }
216        if(defaultDir==null) return null;
217        return _getExistingFile(configDir.getRealResource(defaultDir),type);
218        
219    }
220
221    private static Resource _getExistingFile(Resource file, short type) {
222        
223        boolean asDir=type==ResourceUtil.TYPE_DIR;
224        // File
225        if(file.exists() && ((file.isDirectory() && asDir)||(file.isFile() && !asDir))) {
226            return ResourceUtil.getCanonicalResourceEL(file);
227        }
228        return null;
229    }
230
231    /**
232     * 
233     * @param file
234     * @param type (FileUtil.TYPE_X)
235     * @return created file
236     */
237    public static Resource getFile(Resource file, short type) {
238        return ResourceUtil.createResource(file,ResourceUtil.LEVEL_GRAND_PARENT_FILE,type);
239    }
240
241    /**
242     * checks if file is a directory or not, if directory doesn't exist, it will be created
243     * @param directory
244     * @return is directory or not
245     */
246        public static boolean isDirectory(Resource directory) {
247        if(directory.exists()) return directory.isDirectory();
248        return directory.mkdirs();
249    }
250
251    /**
252     * checks if file is a file or not, if file doesn't exist, it will be created
253     * @param file
254     * @return is file or not
255     */
256    public static boolean isFile(Resource file) {
257        if(file.exists()) return file.isFile();
258        Resource parent=file.getParentResource();
259        return parent.mkdirs() && file.createNewFile();
260    }
261    
262    /**
263     * has access checks if config object has access to given type
264     * @param config
265     * @param type
266     * @return has access
267     */
268    public static boolean hasAccess(Config config, int type) {
269        
270        boolean has=true;
271        if(config instanceof ConfigWeb) {
272            has=((ConfigWeb)config).getSecurityManager().getAccess(type)!=SecurityManager.VALUE_NO;
273        }
274        return has;
275    }
276
277    public static String translateOldPath(String path) {
278        if(path==null) return path;
279        if(path.startsWith("/WEB-INF/lucee/")) {
280            path="{web-root}"+path;
281        }
282        return path;
283    }
284
285        public static Object getIdMapping(Mapping m) {
286                StringBuilder id=new StringBuilder(m.getVirtualLowerCase());
287        if(m.hasPhysical())id.append(m.getStrPhysical());
288        if(m.hasArchive())id.append(m.getStrPhysical());
289        return m.toString().toLowerCase();
290        }
291
292        public static void checkGeneralReadAccess(ConfigImpl config, String password) throws SecurityException {
293                SecurityManager sm = config.getSecurityManager();
294        short access = sm.getAccess(SecurityManager.TYPE_ACCESS_READ);
295        if(config instanceof ConfigServer)access=SecurityManager.ACCESS_PROTECTED;
296        if(access==SecurityManager.ACCESS_PROTECTED) {
297                checkPassword(config,"read",password);
298        }
299        else if(access==SecurityManager.ACCESS_CLOSE) {
300                throw new SecurityException("can't access, read access is disabled");
301        }
302        }
303        
304        public static void checkGeneralWriteAccess(ConfigImpl config, String password) throws SecurityException {
305        SecurityManager sm = config.getSecurityManager();
306        short access = sm.getAccess(SecurityManager.TYPE_ACCESS_WRITE);
307        
308        if(config instanceof ConfigServer)access=SecurityManager.ACCESS_PROTECTED;
309        if(access==SecurityManager.ACCESS_PROTECTED) {
310                checkPassword(config,"write",password);
311        }
312        else if(access==SecurityManager.ACCESS_CLOSE) {
313                throw new SecurityException("can't access, write access is disabled");
314        }
315        }
316
317    public static Password checkPassword(ConfigImpl config, String type,String password) throws SecurityException {
318        if(!config.hasPassword())
319            throw new SecurityException("can't access password protected information from the configuration, no password is defined"); // TODO make the message more clear for someone using the admin indirectly in source code by using ACF specific interfaces
320        Password pw = config.isPasswordEqual(password,true);
321        if(pw==null){
322                if(StringUtil.isEmpty(password)){
323                        if(type==null)
324                                throw new SecurityException("Access is protected",
325                                "to access the configuration without a password, you need to change the access to [open] in the Server Administrator");
326                        throw new SecurityException(type +" access is protected",
327                                "to access the configuration without a password, you need to change the "+type+" access to [open] in the Server Administrator");
328                }
329            throw new SecurityException("No access, password is invalid");
330        }
331        return pw;
332    }
333    
334    public static String createMD5FromResource(Resource resource) throws IOException {
335        InputStream is=null;
336        try{
337                is=resource.getInputStream();   
338                byte[] barr = IOUtil.toBytes(is);
339                return MD5.getDigestAsString(barr);
340        }
341        finally{
342                IOUtil.closeEL(is);
343        }
344    }
345
346        public static int toListenerMode(String strListenerMode, int defaultValue) {
347                if(StringUtil.isEmpty(strListenerMode,true)) return defaultValue;
348                strListenerMode=strListenerMode.trim();
349                
350                if("current".equalsIgnoreCase(strListenerMode) || "curr".equalsIgnoreCase(strListenerMode))             
351                return ApplicationListener.MODE_CURRENT;
352                else if("currenttoroot".equalsIgnoreCase(strListenerMode) || "current2root".equalsIgnoreCase(strListenerMode) || "curr2root".equalsIgnoreCase(strListenerMode))         
353                return ApplicationListener.MODE_CURRENT2ROOT;
354                else if("currentorroot".equalsIgnoreCase(strListenerMode) || "currorroot".equalsIgnoreCase(strListenerMode))            
355                return ApplicationListener.MODE_CURRENT_OR_ROOT;
356        else if("root".equalsIgnoreCase(strListenerMode))               
357                return ApplicationListener.MODE_ROOT;
358                
359                return defaultValue;
360        }
361
362        public static ApplicationListener loadListener(String type, ApplicationListener defaultValue) {
363                 if(StringUtil.isEmpty(type,true)) return defaultValue;
364                 type=type.trim();
365                 
366                 // none
367                 if("none".equalsIgnoreCase(type))      
368                         return new NoneAppListener();
369                 // classic
370                 if("classic".equalsIgnoreCase(type))
371                         return new ClassicAppListener();
372                 // modern
373                 if("modern".equalsIgnoreCase(type))    
374                         return new ModernAppListener();
375                 // mixed
376                 if("mixed".equalsIgnoreCase(type))     
377                         return new MixedAppListener();
378        
379                return defaultValue;
380        }
381
382        public static short inspectTemplate(String str, short defaultValue) { 
383                if(str==null) return defaultValue;
384                str = str.trim().toLowerCase();
385                if (str.equals("always")) return ConfigImpl.INSPECT_ALWAYS;
386                else if (str.equals("never"))return ConfigImpl.INSPECT_NEVER;
387                else if (str.equals("once"))return ConfigImpl.INSPECT_ONCE;
388                return defaultValue;
389        }
390
391
392    public static String inspectTemplate(short s,String defaultValue) {
393        switch(s){
394                case ConfigImpl.INSPECT_ALWAYS: return "always";
395                case ConfigImpl.INSPECT_NEVER: return "never";
396                case ConfigImpl.INSPECT_ONCE: return "once";
397                default: return defaultValue;
398        }
399        }
400
401        public static short toScopeCascading(String type, short defaultValue) {
402                if(StringUtil.isEmpty(type)) return defaultValue;
403        if(type.equalsIgnoreCase("strict")) return Config.SCOPE_STRICT;
404        else if(type.equalsIgnoreCase("small")) return Config.SCOPE_SMALL;
405        else if(type.equalsIgnoreCase("standard"))return Config.SCOPE_STANDARD;
406        else if(type.equalsIgnoreCase("standart"))return Config.SCOPE_STANDARD;
407        return defaultValue;
408        }
409
410        public static String toScopeCascading(short type, String defaultValue) {
411                switch(type){
412                        case Config.SCOPE_STRICT: return "strict";
413                        case Config.SCOPE_SMALL: return "small";
414                        case Config.SCOPE_STANDARD: return "standard";
415                        default: return defaultValue;
416                }
417        }
418        
419
420        public static Mapping[] getAllMappings(PageContext pc) {
421                List<Mapping> list=new ArrayList<Mapping>();
422                getAllMappings(list,pc.getConfig().getMappings());
423                getAllMappings(list,pc.getConfig().getCustomTagMappings());
424                getAllMappings(list,pc.getConfig().getComponentMappings());
425                getAllMappings(list,pc.getApplicationContext().getMappings());
426                return list.toArray(new Mapping[list.size()]);
427        }
428        
429        public static Mapping[] getAllMappings(Config c) {
430                
431                List<Mapping> list=new ArrayList<Mapping>();
432                getAllMappings(list,c.getMappings());
433                getAllMappings(list,c.getCustomTagMappings());
434                getAllMappings(list,c.getComponentMappings());
435                return list.toArray(new Mapping[list.size()]);
436        }
437
438        private static void getAllMappings(List<Mapping> list, Mapping[] mappings) {
439                if(!ArrayUtil.isEmpty(mappings))for(int i=0;i<mappings.length;i++)   {
440                        list.add(mappings[i]);
441                }
442        }
443
444        public static CacheHandlerFactoryCollection getCacheHandlerFactories(ConfigWeb config) { 
445                return ((ConfigWebImpl)config).getCacheHandlerFactories();
446        }
447
448        public static String fixComponentPath(String path) {
449                if(path==null) return path;
450                
451                if(path.startsWith("railo-server-context."))
452                        return "lucee-server"+path.substring(20);
453                if(path.startsWith("railo-context."))
454                        return "lucee-server"+path.substring(13);
455                
456                return path;
457        }
458}