/** * 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.compiler; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.PublicKey; import java.util.Iterator; import java.util.Stack; import java.util.concurrent.ConcurrentLinkedQueue; import lucee.commons.digest.RSA; import lucee.commons.io.IOUtil; import lucee.commons.io.res.Resource; import lucee.commons.lang.StringUtil; import lucee.loader.engine.CFMLEngine; import lucee.runtime.PageSource; import lucee.runtime.PageSourceImpl; import lucee.runtime.config.ConfigImpl; import lucee.runtime.config.Constants; import lucee.runtime.exp.TemplateException; import lucee.transformer.Factory; import lucee.transformer.Position; import lucee.transformer.TransformerException; import lucee.transformer.bytecode.BytecodeFactory; import lucee.transformer.bytecode.Page; import lucee.transformer.bytecode.util.ASMUtil; import lucee.transformer.bytecode.util.ClassRenamer; import lucee.transformer.cfml.tag.CFMLTransformer; import lucee.transformer.library.function.FunctionLib; import lucee.transformer.library.tag.TagLib; import lucee.transformer.util.AlreadyClassException; import lucee.transformer.util.PageSourceCode; import lucee.transformer.util.SourceCode; /** * CFML Compiler compiles CFML source templates */ public final class CFMLCompilerImpl implements CFMLCompiler { private CFMLTransformer cfmlTransformer; private ConcurrentLinkedQueue<WatchEntry> watched=new ConcurrentLinkedQueue<WatchEntry>(); /** * Constructor of the compiler * @param config */ public CFMLCompilerImpl() { cfmlTransformer=new CFMLTransformer(); } public Result compile(ConfigImpl config,PageSource ps, TagLib[] tld, FunctionLib[] fld, Resource classRootDir, boolean returnValue, boolean ignoreScopes) throws TemplateException, IOException { return _compile(config, ps, null,null, tld, fld, classRootDir,returnValue,ignoreScopes); } public Result compile(ConfigImpl config,SourceCode sc, TagLib[] tld, FunctionLib[] fld, Resource classRootDir, String className, boolean returnValue,boolean ignoreScopes) throws TemplateException, IOException { // just to be sure PageSource ps=(sc instanceof PageSourceCode)?((PageSourceCode)sc).getPageSource():null; return _compile(config, ps, sc,className, tld, fld, classRootDir,returnValue,ignoreScopes); } /*private byte[] _compiless(ConfigImpl config,PageSource ps,SourceCode sc,String className, TagLib[] tld, FunctionLib[] fld, Resource classRootDir,TransfomerSettings settings) throws TemplateException { Factory factory = BytecodeFactory.getInstance(config); Page page=null; TagLib[][] _tlibs=new TagLib[][]{null,new TagLib[0]}; _tlibs[CFMLTransformer.TAG_LIB_GLOBAL]=tld; // reset page tlds if(_tlibs[CFMLTransformer.TAG_LIB_PAGE].length>0) { _tlibs[CFMLTransformer.TAG_LIB_PAGE]=new TagLib[0]; } CFMLScriptTransformer scriptTransformer = new CFMLScriptTransformer(); scriptTransformer.transform( BytecodeFactory.getInstance(config) , page , new EvaluatorPool() , _tlibs, fld , null , config.getCoreTagLib(ps.getDialect()).getScriptTags() , sc , settings); //CFMLExprTransformer extr=new CFMLExprTransformer(); //extr.transform(factory, page, ep, tld, fld, scriptTags, cfml, settings) return null; }*/ private Result _compile(ConfigImpl config,PageSource ps,SourceCode sc,String className, TagLib[] tld, FunctionLib[] fld, Resource classRootDir, boolean returnValue, boolean ignoreScopes) throws TemplateException, IOException { if(className==null) className=ps.getClassName(); Result result=null; //byte[] barr = null; Page page = null; Factory factory = BytecodeFactory.getInstance(config); try { page = sc==null? cfmlTransformer.transform(factory,config,ps,tld,fld,returnValue,ignoreScopes): cfmlTransformer.transform(factory,config,sc,tld,fld,System.currentTimeMillis(), sc.getDialect()==CFMLEngine.DIALECT_CFML && config.getDotNotationUpperCase(),returnValue,ignoreScopes); page.setSplitIfNecessary(false); try { result=new Result(page,page.execute(className)); //barr = page.execute(className); } catch(RuntimeException re) { String msg=StringUtil.emptyIfNull(re.getMessage()); if(StringUtil.indexOfIgnoreCase(msg, "Method code too large!")!=-1) { page = sc==null? cfmlTransformer.transform(factory,config,ps,tld,fld,returnValue,ignoreScopes): cfmlTransformer.transform(factory,config,sc,tld,fld, System.currentTimeMillis(), sc.getDialect()==CFMLEngine.DIALECT_CFML && config.getDotNotationUpperCase(),returnValue,ignoreScopes); page.setSplitIfNecessary(true); result = new Result(page,page.execute(className)); } else throw re; } catch(ClassFormatError cfe) { String msg=StringUtil.emptyIfNull(cfe.getMessage()); if(StringUtil.indexOfIgnoreCase(msg, "Invalid method Code length")!=-1) { page = ps!=null? cfmlTransformer.transform(factory,config,ps,tld,fld,returnValue,ignoreScopes): cfmlTransformer.transform(factory,config,sc,tld,fld,System.currentTimeMillis(), sc.getDialect()==CFMLEngine.DIALECT_CFML && config.getDotNotationUpperCase(),returnValue,ignoreScopes); page.setSplitIfNecessary(true); result = new Result(page,page.execute(className)); } else throw cfe; } // store if(classRootDir!=null) { Resource classFile = classRootDir.getRealResource(page.getClassName()+".class"); Resource classFileDirectory=classFile.getParentResource(); if(!classFileDirectory.exists()) classFileDirectory.mkdirs(); IOUtil.copy(new ByteArrayInputStream(result.barr), classFile,true); } return result; } catch (AlreadyClassException ace) { byte[] bytes = ace.getEncrypted()?readEncrypted(ace):readPlain(ace); result = new Result(null,bytes); String displayPath=ps!=null?"["+ps.getDisplayPath()+"] ":""; String srcName = ASMUtil.getClassName(result.barr); int dialect =sc==null?ps.getDialect():sc.getDialect(); // source is cfm and target cfc if(dialect==CFMLEngine.DIALECT_CFML && endsWith(srcName,Constants.getCFMLTemplateExtensions(),dialect) && className.endsWith("_"+Constants.getCFMLComponentExtension()+ (dialect==CFMLEngine.DIALECT_CFML?Constants.CFML_CLASS_SUFFIX:Constants.LUCEE_CLASS_SUFFIX))) { throw new TemplateException("source file "+displayPath+"contains the bytecode for a regular cfm template not for a component"); } // source is cfc and target cfm if(dialect==CFMLEngine.DIALECT_CFML && srcName.endsWith("_"+Constants.getCFMLComponentExtension()+(dialect==CFMLEngine.DIALECT_CFML?Constants.CFML_CLASS_SUFFIX:Constants.LUCEE_CLASS_SUFFIX)) && endsWith(className,Constants.getCFMLTemplateExtensions(),dialect) ) throw new TemplateException("source file "+displayPath+"contains a component not a regular cfm template"); // rename class name when needed if(!srcName.equals(className))result=new Result(result.page, ClassRenamer.rename(result.barr, className)); // store if(classRootDir!=null) { Resource classFile=classRootDir.getRealResource(className+".class"); Resource classFileDirectory=classFile.getParentResource(); if(!classFileDirectory.exists()) classFileDirectory.mkdirs(); result=new Result(result.page, Page.setSourceLastModified(result.barr,ps!=null?ps.getPhyscalFile().lastModified():System.currentTimeMillis())); IOUtil.copy(new ByteArrayInputStream(result.barr), classFile,true); } return result; } catch (TransformerException bce) { Position pos = bce.getPosition(); int line=pos==null?-1:pos.line; int col=pos==null?-1:pos.column; if(ps!=null)bce.addContext(ps, line, col,null); throw bce; } } private byte[] readPlain(AlreadyClassException ace) throws IOException { return IOUtil.toBytes(ace.getInputStream(),true); } private byte[] readEncrypted(AlreadyClassException ace) throws IOException { String str = System.getenv("PUBLIC_KEY"); if(str==null) str=System.getProperty("PUBLIC_KEY"); if(str==null) throw new RuntimeException("to decrypt encrypted bytecode, you need to set PUBLIC_KEY as system property or or enviroment variable"); byte[] bytes = IOUtil.toBytes(ace.getInputStream(),true); try { PublicKey publicKey = RSA.toPublicKey(str); // first 2 bytes are just a mask to detect encrypted code, so we need to set offset 2 bytes=RSA.decrypt(bytes, publicKey,2); } catch (IOException ioe) { throw ioe; } catch (Exception e) { throw new RuntimeException(e); } return bytes; } private boolean endsWith(String name, String[] extensions, int dialect) { for(int i=0;i<extensions.length;i++){ if(name.endsWith("_"+extensions[i]+(dialect==CFMLEngine.DIALECT_CFML?Constants.CFML_CLASS_SUFFIX:Constants.LUCEE_CLASS_SUFFIX))) return true; } return false; } public Page transform(ConfigImpl config,PageSource source, TagLib[] tld, FunctionLib[] fld, boolean returnValue, boolean ignoreScopes) throws TemplateException, IOException { return cfmlTransformer.transform(BytecodeFactory.getInstance(config),config,source,tld,fld,returnValue,ignoreScopes); } public class Result { public Page page; public byte[] barr; public Result(Page page, byte[] barr) { this.page=page; this.barr=barr; } } public void watch(PageSource ps, long now) { watched.offer(new WatchEntry(ps,now,ps.getPhyscalFile().length(),ps.getPhyscalFile().lastModified())); } public void checkWatched() { WatchEntry we; long now=System.currentTimeMillis(); Stack<WatchEntry> tmp =new Stack<WatchEntry>(); while((we=watched.poll())!=null) { // to young if(we.now+1000>now) { tmp.add(we); continue; } if(we.length!=we.ps.getPhyscalFile().length() && we.ps.getPhyscalFile().length()>0) { // TODO this is set to avoid that removed files are removed from pool, remove this line if a UDF still wprks fine when the page is gone ((PageSourceImpl)we.ps).flush(); } } // add again entries that was to young for next round Iterator<WatchEntry> it = tmp.iterator(); while(it.hasNext()) { watched.add(we=it.next()); } } private class WatchEntry { private final PageSource ps; private final long now; private final long length; private final long lastModified; public WatchEntry(PageSource ps, long now, long length, long lastModified) { this.ps=ps; this.now=now; this.length=length; this.lastModified=lastModified; } } }