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}