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.compiler; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.security.PublicKey; 024import java.util.Iterator; 025import java.util.Stack; 026import java.util.concurrent.ConcurrentLinkedQueue; 027 028import lucee.commons.digest.RSA; 029import lucee.commons.io.IOUtil; 030import lucee.commons.io.res.Resource; 031import lucee.commons.lang.StringUtil; 032import lucee.runtime.PageSource; 033import lucee.runtime.PageSourceImpl; 034import lucee.runtime.config.ConfigImpl; 035import lucee.runtime.exp.TemplateException; 036import lucee.transformer.bytecode.BytecodeException; 037import lucee.transformer.bytecode.Page; 038import lucee.transformer.bytecode.Position; 039import lucee.transformer.bytecode.util.ASMUtil; 040import lucee.transformer.bytecode.util.ClassRenamer; 041import lucee.transformer.cfml.tag.CFMLTransformer; 042import lucee.transformer.library.function.FunctionLib; 043import lucee.transformer.library.tag.TagLib; 044import lucee.transformer.util.AlreadyClassException; 045 046 047 048/** 049 * CFML Compiler compiles CFML source templates 050 */ 051public final class CFMLCompilerImpl implements CFMLCompiler { 052 053 054 private CFMLTransformer cfmlTransformer; 055 private ConcurrentLinkedQueue<WatchEntry> watched=new ConcurrentLinkedQueue<WatchEntry>(); 056 057 058 /** 059 * Constructor of the compiler 060 * @param config 061 */ 062 public CFMLCompilerImpl() { 063 cfmlTransformer=new CFMLTransformer(); 064 } 065 066 @Override 067 public byte[] compile(ConfigImpl config,PageSource source, TagLib[] tld, FunctionLib[] fld, 068 Resource classRootDir, String className) throws TemplateException, IOException { 069 //synchronized(source){ 070 Resource classFile=classRootDir.getRealResource(className+".class"); 071 Resource classFileDirectory=classFile.getParentResource(); 072 byte[] barr = null; 073 Page page = null; 074 075 if(!classFileDirectory.exists()) classFileDirectory.mkdirs(); 076 077 try { 078 page = cfmlTransformer.transform(config,source,tld,fld); 079 page.setSplitIfNecessary(false); 080 try { 081 barr = page.execute(source,classFile); 082 } 083 catch(RuntimeException re) { 084 String msg=StringUtil.emptyIfNull(re.getMessage()); 085 if(StringUtil.indexOfIgnoreCase(msg, "Method code too large!")!=-1) { 086 page = cfmlTransformer.transform(config,source,tld,fld); // MUST a new transform is necessary because the page object cannot be reused, rewrite the page that reusing it is possible 087 page.setSplitIfNecessary(true); 088 barr = page.execute(source,classFile); 089 } 090 else throw re; 091 } 092 catch(ClassFormatError cfe) { 093 String msg=StringUtil.emptyIfNull(cfe.getMessage()); 094 if(StringUtil.indexOfIgnoreCase(msg, "Invalid method Code length")!=-1) { 095 page = cfmlTransformer.transform(config,source,tld,fld); // MUST see above 096 page.setSplitIfNecessary(true); 097 barr = page.execute(source,classFile); 098 } 099 else throw cfe; 100 } 101 102 103 104 105 106 107 108 109 110 111 IOUtil.copy(new ByteArrayInputStream(barr), classFile,true); 112 return barr; 113 } 114 catch (AlreadyClassException ace) { 115 116 barr = ace.getEncrypted()?readEncrypted(ace):readPlain(ace); 117 118 String srcName = ASMUtil.getClassName(barr); 119 // source is cfm and target cfc 120 if(srcName.endsWith("_cfm$cf") && className.endsWith("_cfc$cf")) 121 throw new TemplateException("source file ["+source.getDisplayPath()+"] contains the bytecode for a regular cfm template not for a component"); 122 // source is cfc and target cfm 123 if(srcName.endsWith("_cfc$cf") && className.endsWith("_cfm$cf")) 124 throw new TemplateException("source file ["+source.getDisplayPath()+"] contains a component not a regular cfm template"); 125 126 // rename class name when needed 127 if(!srcName.equals(className))barr=ClassRenamer.rename(barr, className); 128 129 130 barr=Page.setSourceLastModified(barr,source.getPhyscalFile().lastModified()); 131 IOUtil.copy(new ByteArrayInputStream(barr), classFile,true); 132 133 return barr; 134 } 135 catch (BytecodeException bce) { 136 Position pos = bce.getPosition(); 137 int line=pos==null?-1:pos.line; 138 int col=pos==null?-1:pos.column; 139 bce.addContext(source, line, col,null); 140 throw bce; 141 //throw new TemplateException(source,e.getLine(),e.getColumn(),e.getMessage()); 142 } 143 /*finally { 144 145 }*/ 146 //} 147 } 148 149 private byte[] readPlain(AlreadyClassException ace) throws IOException { 150 return IOUtil.toBytes(ace.getInputStream(),true); 151 } 152 153 private byte[] readEncrypted(AlreadyClassException ace) throws IOException { 154 155 String str = System.getenv("PUBLIC_KEY"); 156 if(StringUtil.isEmpty(str,true)) str=System.getProperty("PUBLIC_KEY"); 157 if(StringUtil.isEmpty(str,true)) throw new RuntimeException("to decrypt encrypted bytecode, you need to set PUBLIC_KEY as system property or or enviroment variable"); 158 159 System.out.println("PK:"+str); 160 161 byte[] bytes = IOUtil.toBytes(ace.getInputStream(),true); 162 try { 163 PublicKey publicKey = RSA.toPublicKey(str); 164 // first 2 bytes are just a mask to detect encrypted code, so we need to set offset 2 165 bytes=RSA.decrypt(bytes, publicKey,2); 166 } 167 catch (IOException ioe) { 168 throw ioe; 169 } 170 catch (Exception e) { 171 throw new RuntimeException(e); 172 } 173 174 return bytes; 175 } 176 177 public void watch(PageSource ps, long now) { 178 watched.offer(new WatchEntry(ps,now,ps.getPhyscalFile().length(),ps.getPhyscalFile().lastModified())); 179 } 180 181 public void checkWatched() { 182 WatchEntry we; 183 long now=System.currentTimeMillis(); 184 Stack<WatchEntry> tmp =new Stack<WatchEntry>(); 185 while((we=watched.poll())!=null) { 186 // to young 187 if(we.now+1000>now) { 188 tmp.add(we); 189 continue; 190 } 191 if(we.length!=we.ps.getPhyscalFile().length() 192 && 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 193 ((PageSourceImpl)we.ps).flush(); 194 } 195 } 196 197 // add again entries that was to young for next round 198 Iterator<WatchEntry> it = tmp.iterator(); 199 while(it.hasNext()) { 200 watched.add(we=it.next()); 201 } 202 } 203 204 205 private class WatchEntry { 206 207 private final PageSource ps; 208 private final long now; 209 private final long length; 210 //private final long lastModified; 211 212 public WatchEntry(PageSource ps, long now, long length, long lastModified) { 213 this.ps=ps; 214 this.now=now; 215 this.length=length; 216 //this.lastModified=lastModified; 217 } 218 219 } 220}