/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import lucee.commons.digest.MD5;
import lucee.commons.io.IOUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.log.Log;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.filter.ExtensionResourceFilter;
import lucee.commons.io.res.util.ResourceClassLoader;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.SystemOut;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.runtime.Mapping;
import lucee.runtime.PageContext;
import lucee.runtime.crypt.BlowfishEasy;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.SecurityException;
import lucee.runtime.listener.ApplicationListener;
import lucee.runtime.listener.ClassicAppListener;
import lucee.runtime.listener.MixedAppListener;
import lucee.runtime.listener.ModernAppListener;
import lucee.runtime.listener.NoneAppListener;
import lucee.runtime.monitor.Monitor;
import lucee.runtime.net.http.ReqRspUtil;
import lucee.runtime.osgi.BundleBuilderFactory;
import lucee.runtime.osgi.BundleFile;
import lucee.runtime.osgi.OSGiUtil;
import lucee.runtime.security.SecurityManager;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.Struct;
import lucee.runtime.type.util.ArrayUtil;
import org.osgi.framework.BundleContext;
/**
*
*/
public final class ConfigWebUtil {
/**
* default encryption for configuration (not very secure)
* @param str
* @return
*/
public static String decrypt(String str) {
if (StringUtil.isEmpty(str) || !StringUtil.startsWithIgnoreCase(str, "encrypted:"))
return str;
str = str.substring(10);
return new BlowfishEasy("sdfsdfs").decryptString(str);
}
/**
* default encryption for configuration (not very secure)
* @param str
* @return
*/
public static String encrypt(String str) {
if (StringUtil.isEmpty(str))
return "";
if (StringUtil.startsWithIgnoreCase(str, "encrypted:"))
return str;
return "encrypted:" + new BlowfishEasy("sdfsdfs").encryptString(str);
}
/**
* deploys all content in "web-context-deployment" to a web context, used for new context mostly or update existings
* @param cs
* @param cw
* @param throwError
* @throws IOException
*/
public static void deployWebContext(ConfigServer cs, ConfigWeb cw, boolean throwError) throws IOException {
Resource deploy = cs.getConfigDir().getRealResource("web-context-deployment"),trg;
if(!deploy.isDirectory()) return;
trg=cw.getConfigDir().getRealResource("context");
try {
_deployWebContext(cw,deploy,trg);
}
catch (IOException ioe) {
if(throwError) throw ioe;
SystemOut.printDate(cw.getErrWriter(), ExceptionUtil.getStacktrace(ioe, true));
}
}
private static void _deployWebContext(ConfigWeb cw,Resource src, Resource trg) throws IOException {
if(!src.isDirectory())return;
if(trg.isFile()) trg.delete();
if(!trg.exists()) trg.mkdirs();
Resource _src,_trg;
Resource[] children = src.listResources();
if(ArrayUtil.isEmpty(children)) return;
for(int i=0;i<children.length;i++){
_src=children[i];
_trg=trg.getRealResource(_src.getName());
if(_src.isDirectory())
_deployWebContext(cw,_src,_trg);
if(_src.isFile()) {
if(_src.length()!=_trg.length()) {
_src.copyTo(_trg, false);
SystemOut.printDate(cw.getOutWriter(), "write file:" + _trg);
}
}
}
}
public static void reloadLib(Config config) throws IOException {
if (config instanceof ConfigWeb)
loadLib(((ConfigWebImpl) config).getConfigServerImpl(), (ConfigImpl)config);
else
loadLib(null, (ConfigImpl)config);
}
static void loadLib(ConfigServerImpl configServer, ConfigImpl config) throws IOException {
// get lib and classes resources
Resource lib = config.getLibraryDirectory();
Resource[] libs = lib.listResources(ExtensionResourceFilter.EXTENSION_JAR_NO_DIR);
// get resources from server config and merge
if (configServer != null) {
ResourceClassLoader rcl = configServer.getResourceClassLoader();
libs = ResourceUtil.merge(libs, rcl.getResources());
}
CFMLEngine engine = ConfigWebUtil.getEngine(config);
BundleContext bc = engine.getBundleContext();
Log log = config.getLog("application");
BundleFile bf;
List<Resource> list=new ArrayList<Resource>();
for(int i=0;i<libs.length;i++){
try {
bf=BundleFile.newInstance(libs[i]);
// jar is not a bundle
if(bf==null) {
// convert to a bundle
BundleBuilderFactory factory = new BundleBuilderFactory(libs[i]);
factory.setVersion("0.0.0.0");
Resource tmp = SystemUtil.getTempFile("jar", false);
factory.build(tmp);
IOUtil.copy(tmp, libs[i]);
bf=BundleFile.newInstance(libs[i]);
}
OSGiUtil.start(OSGiUtil.installBundle( bc, libs[i],true));
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
list.add(libs[i]);
log.log(Log.LEVEL_ERROR, "OSGi", t);
}
}
// set classloader
ClassLoader parent = SystemUtil.getCoreClassLoader();
config.setResourceClassLoader(new ResourceClassLoader(list.toArray(new Resource[list.size()]), parent));
}
/**
* touch a file object by the string definition
* @param config
* @param directory
* @param path
* @param type
* @return matching file
*/
public static Resource getFile(Config config, Resource directory,String path, short type) {
path=replacePlaceholder(path,config);
if(!StringUtil.isEmpty(path,true)) {
Resource file=getFile(directory.getRealResource(path),type);
if(file!=null) return file;
file=getFile(config.getResource(path),type);
if(file!=null) return file;
}
return null;
}
/**
* generate a file object by the string definition
* @param rootDir
* @param strDir
* @param defaultDir
* @param configDir
* @param type
* @param config
* @return file
*/
static Resource getFile(Resource rootDir,String strDir, String defaultDir,Resource configDir, short type, ConfigImpl config) {
strDir=replacePlaceholder(strDir,config);
if(!StringUtil.isEmpty(strDir,true)) {
Resource res;
if(strDir.indexOf("://")!=-1){ // TODO better impl.
res=getFile(config.getResource(strDir),type);
if(res!=null) return res;
}
res=getFile(rootDir.getRealResource(strDir),type);
if(res!=null) return res;
res=getFile(config.getResource(strDir),type);
if(res!=null) return res;
}
if(defaultDir==null) return null;
Resource file=getFile(configDir.getRealResource(defaultDir),type);
return file;
}
public static String replacePlaceholder(String str, Config config) {
if(StringUtil.isEmpty(str)) return str;
if(StringUtil.startsWith(str,'{')){
// Config Server
if(str.startsWith("{lucee-config")) {
if(str.startsWith("}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(14)));
else if(str.startsWith("-dir}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(18)));
else if(str.startsWith("-directory}",13)) str=checkResult(str,config.getConfigDir().getReal(str.substring(24)));
}
else if(config!=null && str.startsWith("{lucee-server")) {
Resource dir=config instanceof ConfigWeb?((ConfigWeb)config).getConfigServerDir():config.getConfigDir();
//if(config instanceof ConfigServer && cs==null) cs=(ConfigServer) cw;
if(dir!=null) {
if(str.startsWith("}",13)) str=checkResult(str,dir.getReal(str.substring(14)));
else if(str.startsWith("-dir}",13)) str=checkResult(str,dir.getReal(str.substring(18)));
else if(str.startsWith("-directory}",13)) str=checkResult(str,dir.getReal(str.substring(24)));
}
}
// Config Web
else if(str.startsWith("{lucee-web")) {
if(str.startsWith("}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(11)));
else if(str.startsWith("-dir}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(15)));
else if(str.startsWith("-directory}",10)) str=checkResult(str,config.getConfigDir().getReal(str.substring(21)));
}
// Web Root
else if(str.startsWith("{web-root")) {
if(config instanceof ConfigWeb) {
if(str.startsWith("}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(10)));
else if(str.startsWith("-dir}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(14)));
else if(str.startsWith("-directory}",9)) str=checkResult(str,config.getRootDirectory().getReal(str.substring(20)));
}
}
// Temp
else if(str.startsWith("{temp")) {
if(str.startsWith("}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(6)).toString());
else if(str.startsWith("-dir}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(10)).toString());
else if(str.startsWith("-directory}",5)) str=checkResult(str,config.getTempDirectory().getRealResource(str.substring(16)).toString());
}
else if(config instanceof ServletConfig){
Map<String,String> labels=null;
// web
if(config instanceof ConfigWebImpl){
labels=((ConfigWebImpl)config).getAllLabels();
}
// server
else if(config instanceof ConfigServerImpl){
labels=((ConfigServerImpl)config).getLabels();
}
if(labels!=null)str=SystemUtil.parsePlaceHolder(str,((ServletConfig)config).getServletContext(),labels);
}
else str=SystemUtil.parsePlaceHolder(str);
if(StringUtil.startsWith(str,'{')){
Struct constants = ((ConfigImpl)config).getConstants();
Iterator<Entry<Key, Object>> it = constants.entryIterator();
Entry<Key, Object> e;
while(it.hasNext()) {
e = it.next();
if(StringUtil.startsWithIgnoreCase(str,"{"+e.getKey().getString()+"}")) {
String value=(String) e.getValue();
str=checkResult(str,config.getResource( value)
.getReal(str.substring(e.getKey().getString().length()+2)));
break;
}
}
}
}
return str;
}
private static String checkResult(String src, String res) {
boolean srcEndWithSep=StringUtil.endsWith(src, ResourceUtil.FILE_SEPERATOR) || StringUtil.endsWith(src, '/') || StringUtil.endsWith(src, '\\');
boolean resEndWithSep=StringUtil.endsWith(res, ResourceUtil.FILE_SEPERATOR) || StringUtil.endsWith(res, '/') || StringUtil.endsWith(res, '\\');
if(srcEndWithSep && !resEndWithSep) return res+ResourceUtil.FILE_SEPERATOR;
if(!srcEndWithSep && resEndWithSep) return res.substring(0,res.length()-1);
return res;
}
/**
* get only a existing file, dont create it
* @param sc
* @param strDir
* @param defaultDir
* @param configDir
* @param type
* @param config
* @return existing file
*/
public static Resource getExistingResource(ServletContext sc,String strDir, String defaultDir,Resource configDir, short type, Config config) {
//ARP
strDir=replacePlaceholder(strDir,config);
if(strDir!=null && strDir.trim().length()>0) {
Resource res=sc==null?null:_getExistingFile(config.getResource(ResourceUtil.merge(ReqRspUtil.getRootPath(sc),strDir)),type);
if(res!=null) return res;
res=_getExistingFile(config.getResource(strDir),type);
if(res!=null) return res;
}
if(defaultDir==null) return null;
return _getExistingFile(configDir.getRealResource(defaultDir),type);
}
private static Resource _getExistingFile(Resource file, short type) {
boolean asDir=type==ResourceUtil.TYPE_DIR;
// File
if(file.exists() && ((file.isDirectory() && asDir)||(file.isFile() && !asDir))) {
return ResourceUtil.getCanonicalResourceEL(file);
}
return null;
}
/**
*
* @param file
* @param type (FileUtil.TYPE_X)
* @return created file
*/
public static Resource getFile(Resource file, short type) {
return ResourceUtil.createResource(file,ResourceUtil.LEVEL_GRAND_PARENT_FILE,type);
}
/**
* checks if file is a directory or not, if directory doesn't exist, it will be created
* @param directory
* @return is directory or not
*/
public static boolean isDirectory(Resource directory) {
if(directory.exists()) return directory.isDirectory();
return directory.mkdirs();
}
/**
* checks if file is a file or not, if file doesn't exist, it will be created
* @param file
* @return is file or not
*/
public static boolean isFile(Resource file) {
if(file.exists()) return file.isFile();
Resource parent=file.getParentResource();
return parent.mkdirs() && file.createNewFile();
}
/**
* has access checks if config object has access to given type
* @param config
* @param type
* @return has access
*/
public static boolean hasAccess(Config config, int type) {
boolean has=true;
if(config instanceof ConfigWeb) {
has=((ConfigWeb)config).getSecurityManager().getAccess(type)!=SecurityManager.VALUE_NO;
}
return has;
}
public static String translateOldPath(String path) {
if(path==null) return path;
if(path.startsWith("/WEB-INF/lucee/")) {
path="{web-root}"+path;
}
return path;
}
public static Object getIdMapping(Mapping m) {
StringBuilder id=new StringBuilder(m.getVirtualLowerCase());
if(m.hasPhysical())id.append(m.getStrPhysical());
if(m.hasArchive())id.append(m.getStrPhysical());
return m.toString().toLowerCase();
}
public static void checkGeneralReadAccess(ConfigImpl config, Password password) throws SecurityException {
SecurityManager sm = config.getSecurityManager();
short access = sm.getAccess(SecurityManager.TYPE_ACCESS_READ);
if(config instanceof ConfigServer)access=SecurityManager.ACCESS_PROTECTED;
if(access==SecurityManager.ACCESS_PROTECTED) {
checkPassword(config,"read",password);
}
else if(access==SecurityManager.ACCESS_CLOSE) {
throw new SecurityException("can't access, read access is disabled");
}
}
public static void checkGeneralWriteAccess(ConfigImpl config, Password password) throws SecurityException {
SecurityManager sm = config.getSecurityManager();
short access = sm.getAccess(SecurityManager.TYPE_ACCESS_WRITE);
if(config instanceof ConfigServer)access=SecurityManager.ACCESS_PROTECTED;
if(access==SecurityManager.ACCESS_PROTECTED) {
checkPassword(config,"write",password);
}
else if(access==SecurityManager.ACCESS_CLOSE) {
throw new SecurityException("can't access, write access is disabled");
}
}
public static void checkPassword(ConfigImpl config, String type,Password password) throws SecurityException {
if(!config.hasPassword())
throw new SecurityException("can't access password protected information from the configuration, no password is defined for "+(config instanceof ConfigServer?"the server context":"this web context") ); // TODO make the message more clear for someone using the admin indirectly in source code by using ACF specific interfaces
if(!config.passwordEqual(password)){
if(StringUtil.isEmpty(password)){
if(type==null)
throw new SecurityException("Access is protected",
"to access the configuration without a password, you need to change the access to [open] in the Server Administrator");
throw new SecurityException(type +" access is protected",
"to access the configuration without a password, you need to change the "+type+" access to [open] in the Server Administrator");
}
throw new SecurityException("No access, password is invalid");
}
}
public static String createMD5FromResource(Resource resource) throws IOException {
InputStream is=null;
try{
is=resource.getInputStream();
byte[] barr = IOUtil.toBytes(is);
return MD5.getDigestAsString(barr);
}
finally{
IOUtil.closeEL(is);
}
}
public static int toListenerMode(String strListenerMode, int defaultValue) {
if(StringUtil.isEmpty(strListenerMode,true)) return defaultValue;
strListenerMode=strListenerMode.trim();
if("current".equalsIgnoreCase(strListenerMode) || "curr".equalsIgnoreCase(strListenerMode))
return ApplicationListener.MODE_CURRENT;
else if("currenttoroot".equalsIgnoreCase(strListenerMode) || "current2root".equalsIgnoreCase(strListenerMode) || "curr2root".equalsIgnoreCase(strListenerMode))
return ApplicationListener.MODE_CURRENT2ROOT;
else if("currentorroot".equalsIgnoreCase(strListenerMode) || "currorroot".equalsIgnoreCase(strListenerMode))
return ApplicationListener.MODE_CURRENT_OR_ROOT;
else if("root".equalsIgnoreCase(strListenerMode))
return ApplicationListener.MODE_ROOT;
return defaultValue;
}
public static String toListenerMode(int listenerMode, String defaultValue) {
if(ApplicationListener.MODE_CURRENT==listenerMode) return "current";
else if(ApplicationListener.MODE_CURRENT2ROOT==listenerMode) return "curr2root";
else if(ApplicationListener.MODE_CURRENT_OR_ROOT==listenerMode) return "currorroot";
else if(ApplicationListener.MODE_ROOT==listenerMode) return "root";
return defaultValue;
}
public static int toListenerType(String strListenerType, int defaultValue) {
if(StringUtil.isEmpty(strListenerType,true)) return defaultValue;
strListenerType=strListenerType.trim();
if("none".equalsIgnoreCase(strListenerType))
return ApplicationListener.TYPE_NONE;
else if("classic".equalsIgnoreCase(strListenerType))
return ApplicationListener.TYPE_CLASSIC;
else if("modern".equalsIgnoreCase(strListenerType))
return ApplicationListener.TYPE_MODERN;
else if("mixed".equalsIgnoreCase(strListenerType))
return ApplicationListener.TYPE_MIXED;
return defaultValue;
}
public static String toListenerType(int listenerType, String defaultValue) {
if(ApplicationListener.TYPE_NONE==listenerType) return "none";
else if(ApplicationListener.TYPE_CLASSIC==listenerType) return "classic";
else if(ApplicationListener.TYPE_MODERN==listenerType) return "modern";
else if(ApplicationListener.TYPE_MIXED==listenerType) return "mixed";
return defaultValue;
}
public static ApplicationListener loadListener(String type, ApplicationListener defaultValue) {
return loadListener(toListenerType(type, -1), defaultValue);
}
public static ApplicationListener loadListener(int type, ApplicationListener defaultValue) {
// none
if(ApplicationListener.TYPE_NONE==type)
return new NoneAppListener();
// classic
if(ApplicationListener.TYPE_CLASSIC==type)
return new ClassicAppListener();
// modern
if(ApplicationListener.TYPE_MODERN==type)
return new ModernAppListener();
// mixed
if(ApplicationListener.TYPE_MIXED==type)
return new MixedAppListener();
return defaultValue;
}
public static short inspectTemplate(String str, short defaultValue) {
if(str==null) return defaultValue;
str = str.trim().toLowerCase();
if (str.equals("always")) return Config.INSPECT_ALWAYS;
else if (str.equals("never"))return Config.INSPECT_NEVER;
else if (str.equals("once"))return Config.INSPECT_ONCE;
return defaultValue;
}
public static String inspectTemplate(short s,String defaultValue) {
switch(s){
case Config.INSPECT_ALWAYS: return "always";
case Config.INSPECT_NEVER: return "never";
case Config.INSPECT_ONCE: return "once";
default: return defaultValue;
}
}
public static short toScopeCascading(String type, short defaultValue) {
if(StringUtil.isEmpty(type)) return defaultValue;
if(type.equalsIgnoreCase("strict")) return Config.SCOPE_STRICT;
else if(type.equalsIgnoreCase("small")) return Config.SCOPE_SMALL;
else if(type.equalsIgnoreCase("standard"))return Config.SCOPE_STANDARD;
else if(type.equalsIgnoreCase("standart"))return Config.SCOPE_STANDARD;
return defaultValue;
}
public static short toScopeCascading(boolean searchImplicitScopes) {
if(searchImplicitScopes) return Config.SCOPE_STANDARD;
return Config.SCOPE_STRICT;
}
public static String toScopeCascading(short type, String defaultValue) {
switch(type){
case Config.SCOPE_STRICT: return "strict";
case Config.SCOPE_SMALL: return "small";
case Config.SCOPE_STANDARD: return "standard";
default: return defaultValue;
}
}
public static CFMLEngine getEngine(Config config) {
if(config instanceof ConfigWeb) return ((ConfigWeb)config).getFactory().getEngine();
if(config instanceof ConfigServer) return ((ConfigServer)config).getEngine();
return CFMLEngineFactory.getInstance();
}
public static Resource getConfigServerDirectory(Config config) {
if(config==null)config=ThreadLocalPageContext.getConfig();
if(config instanceof ConfigWeb) return ((ConfigWeb)config).getConfigServerDir();
if(config==null) return null;
return ((ConfigServer)config).getConfigDir();
}
public static Mapping[] getAllMappings(PageContext pc) {
List<Mapping> list=new ArrayList<Mapping>();
getAllMappings(list,pc.getConfig().getMappings());
getAllMappings(list,pc.getConfig().getCustomTagMappings());
getAllMappings(list,pc.getConfig().getComponentMappings());
getAllMappings(list,pc.getApplicationContext().getMappings());
return list.toArray(new Mapping[list.size()]);
}
public static Mapping[] getAllMappings(Config cw) {
List<Mapping> list=new ArrayList<Mapping>();
getAllMappings(list,cw.getMappings());
getAllMappings(list,cw.getCustomTagMappings());
getAllMappings(list,cw.getComponentMappings());
return list.toArray(new Mapping[list.size()]);
}
private static void getAllMappings(List<Mapping> list, Mapping[] mappings) {
if(!ArrayUtil.isEmpty(mappings))for(int i=0;i<mappings.length;i++) {
list.add(mappings[i]);
}
}
public static int toDialect(String strDialect, int defaultValue) {
if("cfml".equalsIgnoreCase(strDialect))return CFMLEngine.DIALECT_CFML;
if("cfm".equalsIgnoreCase(strDialect))return CFMLEngine.DIALECT_CFML;
if("cfc".equalsIgnoreCase(strDialect))return CFMLEngine.DIALECT_CFML;
if("lucee".equalsIgnoreCase(strDialect))return CFMLEngine.DIALECT_LUCEE;
return defaultValue;
}
public static String toDialect(int dialect, String defaultValue) {
if(dialect==CFMLEngine.DIALECT_CFML)return "cfml";
if(dialect==CFMLEngine.DIALECT_LUCEE)return "lucee";
return defaultValue;
}
public static int toMonitorType(String type, int defaultValue) {
if(type==null) return defaultValue;
type=type.trim();
if ("request".equalsIgnoreCase(type))
return Monitor.TYPE_REQUEST;
else if ("action".equalsIgnoreCase(type))
return Monitor.TYPE_ACTION;
else if ("interval".equalsIgnoreCase(type) || "intervall".equalsIgnoreCase(type))
return Monitor.TYPE_INTERVAL;
return defaultValue;
}
}