package railo.runtime; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import railo.commons.io.CharsetUtil; import railo.commons.io.IOUtil; import railo.commons.io.res.Resource; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.SizeOf; import railo.commons.lang.StringUtil; import railo.commons.lang.types.RefBoolean; import railo.commons.lang.types.RefBooleanImpl; import railo.runtime.config.ConfigImpl; import railo.runtime.config.ConfigWeb; import railo.runtime.config.ConfigWebImpl; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.engine.ThreadLocalPageSource; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.MissingIncludeException; import railo.runtime.exp.PageException; import railo.runtime.exp.TemplateException; import railo.runtime.functions.system.GetDirectoryFromPath; import railo.runtime.op.Caster; import railo.runtime.type.Sizeable; import railo.runtime.type.util.ArrayUtil; import railo.runtime.type.util.ListUtil; /** * represent a cfml file on the runtime system */ public final class PageSourceImpl implements SourceFile, PageSource, Sizeable { private static final long serialVersionUID = -7661676586215092539L; //public static final byte LOAD_NONE=1; public static final byte LOAD_ARCHIVE=2; public static final byte LOAD_PHYSICAL=3; //private byte load=LOAD_NONE; private final MappingImpl mapping; private final String realPath; private boolean isOutSide; private String className; private String packageName; private String javaName; private Resource physcalSource; private Resource archiveSource; private String fileName; private String compName; private Page page; private long lastAccess; private int accessCount=0; //private boolean recompileAlways; //private boolean recompileAfterStartUp; private PageSourceImpl() { mapping=null; realPath=null; } /** * constructor of the class * @param mapping * @param realPath */ PageSourceImpl(MappingImpl mapping,String realPath) { this.mapping=mapping; //recompileAlways=mapping.getConfig().getCompileType()==Config.RECOMPILE_ALWAYS; //recompileAfterStartUp=mapping.getConfig().getCompileType()==Config.RECOMPILE_AFTER_STARTUP || recompileAlways; realPath=realPath.replace('\\','/'); if(realPath.indexOf('/')!=0) { if(realPath.startsWith("../")) { isOutSide=true; } else if(realPath.startsWith("./")) { realPath=realPath.substring(1); } else { realPath="/"+realPath; } } this.realPath=realPath; } /** * private constructor of the class * @param mapping * @param realPath * @param isOutSide */ PageSourceImpl(MappingImpl mapping, String realPath, boolean isOutSide) { //recompileAlways=mapping.getConfig().getCompileType()==Config.RECOMPILE_ALWAYS; //recompileAfterStartUp=mapping.getConfig().getCompileType()==Config.RECOMPILE_AFTER_STARTUP || recompileAlways; this.mapping=mapping; this.isOutSide=isOutSide; this.realPath=realPath; } /** * return page when already loaded, otherwise null * @param pc * @param config * @return * @throws PageException */ public Page getPage() { return page; } public PageSource getParent(){ if(realPath.equals("/")) return null; if(StringUtil.endsWith(realPath, '/')) return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(realPath.substring(0, realPath.length()-1))); return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(realPath)); } @Override public Page loadPage(ConfigWeb config) throws PageException { return loadPage(ThreadLocalPageContext.get()); } @Override public Page loadPage(ConfigWeb config, Page defaultValue) throws PageException { return loadPage(ThreadLocalPageContext.get(), defaultValue); } public Page loadPage(PageContext pc, boolean forceReload) throws PageException { if(forceReload) page=null; return loadPage(pc); } public Page loadPage(PageContext pc) throws PageException { Page page=this.page; if(mapping.isPhysicalFirst()) { page=loadPhysical(pc,page); if(page==null) page=loadArchive(page); if(page!=null) return page; } else { page=loadArchive(page); if(page==null)page=loadPhysical(pc,page); if(page!=null) return page; } throw new MissingIncludeException(this); } @Override public Page loadPage(PageContext pc, Page defaultValue) throws PageException { Page page=this.page; if(mapping.isPhysicalFirst()) { page=loadPhysical(pc,page); if(page==null) page=loadArchive(page); if(page!=null) return page; } else { page=loadArchive(page); if(page==null)page=loadPhysical(pc,page); if(page!=null) return page; } return defaultValue; } private Page loadArchive(Page page) { if(!mapping.hasArchive()) return null; if(page!=null && page.getLoadType()==LOAD_ARCHIVE) return page; try { synchronized(this) { Class clazz=mapping.getClassLoaderForArchive().loadClass(getClazz()); this.page=page=newInstance(clazz); page.setPageSource(this); //page.setTimeCreated(System.currentTimeMillis()); page.setLoadType(LOAD_ARCHIVE); ////load=LOAD_ARCHIVE; return page; } } catch (Exception e) { return null; } } private Page loadPhysical(PageContext pc,Page page) throws PageException { if(!mapping.hasPhysical()) return null; ConfigWeb config=pc.getConfig(); PageContextImpl pci=(PageContextImpl) pc; if((mapping.getInspectTemplate()==ConfigImpl.INSPECT_NEVER || pci.isTrusted(page)) && isLoad(LOAD_PHYSICAL)) return page; Resource srcFile = getPhyscalFile(); /*{ String dp = getDisplayPath(); String cn = getClassName(); if(dp.endsWith(".cfc") && cn.startsWith("cfc")) { print.ds("->"+dp); print.e("trusted:"+mapping.isTrusted()); print.e(mapping.getVirtual()); print.e("mod:"+srcFile.lastModified()); } }*/ long srcLastModified = srcFile.lastModified(); if(srcLastModified==0L) return null; // Page exists if(page!=null) { //if(page!=null && !recompileAlways) { // java file is newer !mapping.isTrusted() && if(srcLastModified!=page.getSourceLastModified()) { this.page=page=compile(config,mapping.getClassRootDirectory(),Boolean.TRUE); page.setPageSource(this); page.setLoadType(LOAD_PHYSICAL); } } // page doesn't exist else { ///synchronized(this) { Resource classRootDir=mapping.getClassRootDirectory(); Resource classFile=classRootDir.getRealResource(getJavaName()+".class"); boolean isNew=false; // new class if(!classFile.exists()) { //if(!classFile.exists() || recompileAfterStartUp) { this.page=page= compile(config,classRootDir,Boolean.FALSE); isNew=true; } // load page else { try { this.page=page=newInstance(mapping.touchPCLCollection().getClass(this)); } catch (Throwable t) {t.printStackTrace(); this.page=page=null; } if(page==null) this.page=page=compile(config,classRootDir,Boolean.TRUE); } // check if there is a newwer version if(!isNew && srcLastModified!=page.getSourceLastModified()) { isNew=true; this.page=page=compile(config,classRootDir,null); } // check version if(!isNew && page.getVersion()!=Info.getFullVersionInfo()) { isNew=true; this.page=page=compile(config,classRootDir,null); } page.setPageSource(this); page.setLoadType(LOAD_PHYSICAL); } pci.setPageUsed(page); return page; } private boolean isLoad(byte load) { return page!=null && load==page.getLoadType(); } private synchronized Page compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws PageException { try { return _compile(config, classRootDir, resetCL); } catch(ClassFormatError e) { String msg=StringUtil.emptyIfNull(e.getMessage()); if(StringUtil.indexOfIgnoreCase(msg, "Invalid method Code length")!=-1) { throw new TemplateException("There is too much code inside the template ["+getDisplayPath()+"], Railo was not able to break it into pieces, move parts of your code to an include or a external component/function",msg); } throw Caster.toPageException(e); } catch(Throwable t) { throw Caster.toPageException(t); } } private Page _compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws IOException, SecurityException, IllegalArgumentException, PageException { ConfigWebImpl cwi=(ConfigWebImpl) config; byte[] barr = cwi.getCompiler(). compile(cwi,this,cwi.getTLDs(),cwi.getFLDs(),classRootDir,getJavaName()); Class<?> clazz = mapping.touchPCLCollection().loadClass(getClazz(), barr,isComponent()); try{ return newInstance(clazz); } catch(Throwable t){ PageException pe = Caster.toPageException(t); pe.setExtendedInfo("failed to load template "+getDisplayPath()); throw pe; } } private Page newInstance(Class clazz) throws SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { try{ Constructor c = clazz.getConstructor(new Class[]{PageSource.class}); return (Page) c.newInstance(new Object[]{this}); } // this only happens with old code from ra files catch(NoSuchMethodException e){ ThreadLocalPageSource.register(this); try{ return (Page) clazz.newInstance(); } finally { ThreadLocalPageSource.release(); } } } /** * return source path as String * @return source path as String */ public String getDisplayPath() { if(!mapping.hasArchive()) { return StringUtil.toString(getPhyscalFile(), null); } else if(isLoad(LOAD_PHYSICAL)) { return StringUtil.toString(getPhyscalFile(), null); } else if(isLoad(LOAD_ARCHIVE)) { return StringUtil.toString(getArchiveSourcePath(), null); } else { boolean pse = physcalExists(); boolean ase = archiveExists(); if(mapping.isPhysicalFirst()) { if(pse)return getPhyscalFile().toString(); else if(ase)return getArchiveSourcePath(); return getPhyscalFile().toString(); } if(ase)return getArchiveSourcePath(); else if(pse)return getPhyscalFile().toString(); return getArchiveSourcePath(); } } public boolean isComponent() { return ResourceUtil.getExtension(getRealpath(), "").equalsIgnoreCase(mapping.getConfig().getCFCExtension()); } /** * return file object, based on physical path and realpath * @return file Object */ private String getArchiveSourcePath() { return "zip://"+mapping.getArchive().getAbsolutePath()+"!"+realPath; } /** * return file object, based on physical path and realpath * @return file Object */ public Resource getPhyscalFile() { if(physcalSource==null) { if(!mapping.hasPhysical()) { return null; } physcalSource=ResourceUtil.toExactResource(mapping.getPhysical().getRealResource(realPath)); } return physcalSource; } public Resource getArchiveFile() { if(archiveSource==null) { if(!mapping.hasArchive()) return null; String path="zip://"+mapping.getArchive().getAbsolutePath()+"!"+realPath; archiveSource = ThreadLocalPageContext.getConfig().getResource(path); } return archiveSource; } /** * merge to realpath to one * @param mapping * @param parentRealPath * @param newRealPath * @param isOutSide * @return merged realpath */ private static String mergeRealPathes(Mapping mapping,String parentRealPath, String newRealPath, RefBoolean isOutSide) { parentRealPath=pathRemoveLast(parentRealPath,isOutSide); while(newRealPath.startsWith("../")) { parentRealPath=pathRemoveLast(parentRealPath,isOutSide); newRealPath=newRealPath.substring(3); } // check if come back String path=parentRealPath.concat("/").concat(newRealPath); if(path.startsWith("../")) { int count=0; do { count++; path=path.substring(3); }while(path.startsWith("../")); String strRoot=mapping.getPhysical().getAbsolutePath().replace('\\','/'); if(!StringUtil.endsWith(strRoot,'/')) { strRoot+='/'; } int rootLen=strRoot.length(); String[] arr=ListUtil.toStringArray(ListUtil.listToArray(path,'/'),"");//path.split("/"); int tmpLen; for(int i=count;i>0;i--) { if(arr.length>i) { String tmp='/'+list(arr,0,i); tmpLen=rootLen-tmp.length(); if(strRoot.lastIndexOf(tmp)==tmpLen && tmpLen>=0) { StringBuffer rtn=new StringBuffer(); while(i<count-i) { count--; rtn.append("../"); } isOutSide.setValue(rtn.length()!=0); return (rtn.length()==0?"/":rtn.toString())+list(arr,i,arr.length); } } } } return parentRealPath.concat("/").concat(newRealPath); } /** * convert a String array to a string list, but only part of it * @param arr String Array * @param from start from here * @param len how many element * @return String list */ private static String list(String[] arr,int from, int len) { StringBuffer sb=new StringBuffer(); for(int i=from;i<len;i++) { sb.append(arr[i]); if(i+1!=arr.length)sb.append('/'); } return sb.toString(); } /** * remove the last elemtn of a path * @param path path to remove last element from it * @param isOutSide * @return path with removed element */ private static String pathRemoveLast(String path, RefBoolean isOutSide) { if(path.length()==0) { isOutSide.setValue(true); return ".."; } else if(path.endsWith("..")){ isOutSide.setValue(true); return path.concat("/..");//path+"/.."; } return path.substring(0,path.lastIndexOf('/')); } @Override public String getRealpath() { return realPath; } @Override public String getFullRealpath() { if(mapping.getVirtual().length()==1 || mapping.ignoreVirtual()) return realPath; return mapping.getVirtual()+realPath; } /** * @return returns a variable string based on realpath and return it */ public String getRealPathAsVariableString() { return StringUtil.toIdentityVariableName(realPath); } @Override public String getClazz() { if(className==null) createClassAndPackage(); if(packageName.length()>0) return packageName+'.'+className; return className; } /** * @return returns the a classname matching to filename (Example: test_cfm) */ public String getClassName() { if(className==null) createClassAndPackage(); return className; } @Override public String getFileName() { if(fileName==null) createClassAndPackage(); return fileName; } @Override public String getJavaName() { if(javaName==null) createClassAndPackage(); return javaName; } /** * @return returns the a package matching to file (Example: railo.web) */ public String getPackageName() { if(packageName==null) createClassAndPackage(); return packageName; } @Override public String getComponentName() { if(compName==null) createComponentName(); return compName; } private synchronized void createClassAndPackage() { String str=realPath; StringBuffer packageName=new StringBuffer(); StringBuffer javaName=new StringBuffer(); String[] arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/')); String varName; for(int i=0;i<arr.length;i++) { if(i==(arr.length-1)) { int index=arr[i].lastIndexOf('.'); if(index!=-1){ String ext=arr[i].substring(index+1); varName=StringUtil.toVariableName(arr[i].substring(0,index)+"_"+ext); } else varName=StringUtil.toVariableName(arr[i]); varName=varName+"$cf"; className=varName.toLowerCase(); fileName=arr[i]; } else { varName=StringUtil.toVariableName(arr[i]); if(i!=0) { packageName.append('.'); } packageName.append(varName); } javaName.append('/'); javaName.append(varName); } this.packageName=packageName.toString().toLowerCase(); this.javaName=javaName.toString().toLowerCase(); } private synchronized void createComponentName() { Resource res = this.getPhyscalFile(); String str=null; if(res!=null) { str=res.getAbsolutePath(); str=str.substring(str.length()-realPath.length()); if(!str.equalsIgnoreCase(realPath)) { str=realPath; } } else str=realPath; StringBuffer compName=new StringBuffer(); String[] arr; // virtual part if(!mapping.ignoreVirtual()) { arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(mapping.getVirtual(),"\\/")); for(int i=0;i<arr.length;i++) { if(compName.length()>0) compName.append('.'); compName.append(arr[i]); } } // physical part arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/')); for(int i=0;i<arr.length;i++) { if(compName.length()>0) compName.append('.'); if(i==(arr.length-1)) { compName.append(arr[i].substring(0,arr[i].length()-4)); } else compName.append(arr[i]); } this.compName=compName.toString(); } @Override public Mapping getMapping() { return mapping; } @Override public boolean exists() { if(mapping.isPhysicalFirst()) return physcalExists() || archiveExists(); return archiveExists() || physcalExists(); } @Override public boolean physcalExists() { return ResourceUtil.exists(getPhyscalFile()); } private boolean archiveExists() { if(!mapping.hasArchive())return false; try { String clazz = getClazz(); if(clazz==null) return getArchiveFile().exists(); mapping.getClassLoaderForArchive().loadClass(clazz); return true; } catch(ClassNotFoundException cnfe){ return false; } catch (Exception e) { return getArchiveFile().exists(); } } /** * return the inputstream of the source file * @return return the inputstream for the source from ohysical or archive * @throws FileNotFoundException */ private InputStream getSourceAsInputStream() throws IOException { if(!mapping.hasArchive()) return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream()); else if(isLoad(LOAD_PHYSICAL)) return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream()); else if(isLoad(LOAD_ARCHIVE)) { StringBuffer name=new StringBuffer(getPackageName().replace('.','/')); if(name.length()>0)name.append("/"); name.append(getFileName()); return mapping.getClassLoaderForArchive().getResourceAsStream(name.toString()); } else { return null; } } @Override public String[] getSource() throws IOException { //if(source!=null) return source; InputStream is = getSourceAsInputStream(); if(is==null) return null; try { return IOUtil.toStringArray(IOUtil.getReader(is,CharsetUtil.toCharset(getMapping().getConfig().getTemplateCharset()))); } finally { IOUtil.closeEL(is); } } @Override public boolean equals(Object obj) { if(this==obj) return true; if(!(obj instanceof PageSource)) return false; return getClassName().equals(((PageSource)obj).getClassName()); //return equals((PageSource)obj); } /** * is given object equal to this * @param other * @return is same */ public boolean equals(PageSource other) { if(this==other) return true; return getClassName().equals(other.getClassName()); } @Override public PageSource getRealPage(String realPath) { if(realPath.equals(".") || realPath.equals(".."))realPath+='/'; else realPath=realPath.replace('\\','/'); RefBoolean _isOutSide=new RefBooleanImpl(isOutSide); if(realPath.indexOf('/')==0) { _isOutSide.setValue(false); } else if(realPath.startsWith("./")) { realPath=mergeRealPathes(mapping,this.realPath, realPath.substring(2),_isOutSide); } else { realPath=mergeRealPathes(mapping,this.realPath, realPath,_isOutSide); } return mapping.getPageSource(realPath,_isOutSide.toBooleanValue()); } @Override public final void setLastAccessTime(long lastAccess) { this.lastAccess=lastAccess; } @Override public final long getLastAccessTime() { return lastAccess; } @Override public synchronized final void setLastAccessTime() { accessCount++; this.lastAccess=System.currentTimeMillis(); } @Override public final int getAccessCount() { return accessCount; } @Override public Resource getResource() { Resource p = getPhyscalFile(); Resource a = getArchiveFile(); if(mapping.isPhysicalFirst()){ if(a==null) return p; if(p==null) return a; if(p.exists()) return p; if(a.exists()) return a; return p; } if(p==null) return a; if(a==null) return p; if(a.exists()) return a; if(p.exists()) return p; return a; //return getArchiveFile(); } @Override public Resource getResourceTranslated(PageContext pc) throws ExpressionException { Resource res = null; if(!isLoad(LOAD_ARCHIVE)) res=getPhyscalFile(); // there is no physical resource if(res==null){ String path=getDisplayPath(); if(path!=null){ if(path.startsWith("ra://")) path="zip://"+path.substring(5); res=ResourceUtil.toResourceNotExisting(pc, path,false); } } return res; } public void clear() { if(page!=null){ page=null; } } /** * clear page, but only when page use the same clasloader as provided * @param cl */ public void clear(ClassLoader cl) { if(page!=null && page.getClass().getClassLoader().equals(cl)){ page=null; } } @Override public String getFullClassName() { String s=_getFullClassName(); return s; } public String _getFullClassName() { String p=getPackageName(); if(p.length()==0) return getClassName(); return p.concat(".").concat(getClassName()); } public boolean isLoad() { return page!=null;////load!=LOAD_NONE; } @Override public String toString() { return getDisplayPath(); } @Override public long sizeOf() { return SizeOf.size(page,0)+ SizeOf.size(className)+ SizeOf.size(packageName)+ SizeOf.size(javaName)+ SizeOf.size(fileName)+ SizeOf.size(compName)+ SizeOf.size(lastAccess)+ SizeOf.size(accessCount); } public static PageSource best(PageSource[] arr) { if(ArrayUtil.isEmpty(arr)) return null; if(arr.length==1)return arr[0]; for(int i=0;i<arr.length;i++) { if(pageExist(arr[i])) return arr[i]; } /*// get the best none existing for(int i=0;i<arr.length;i++) { if(arr[i].getPhyscalFile()!=null) return arr[i]; } for(int i=0;i<arr.length;i++) { if(arr[i].getDisplayPath()!=null) return arr[i]; } */ return arr[0]; } public static boolean pageExist(PageSource ps) { return (ps.getMapping().isTrusted() && ((PageSourceImpl)ps).isLoad()) || ps.exists(); } public static Page loadPage(PageContext pc,PageSource[] arr,Page defaultValue) throws PageException { if(ArrayUtil.isEmpty(arr)) return null; Page p; for(int i=0;i<arr.length;i++) { p=arr[i].loadPage(pc,(Page)null); if(p!=null) return p; } return defaultValue; } public static Page loadPage(PageContext pc,PageSource[] arr) throws PageException { if(ArrayUtil.isEmpty(arr)) return null; Page p; for(int i=0;i<arr.length;i++) { p=arr[i].loadPage(pc,null); if(p!=null) return p; } throw new MissingIncludeException(arr[0]); } }