package railo.runtime.tag; import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_ERROR; import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_OVERWRITE; import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_SKIP; import static railo.runtime.tag.util.FileUtil.NAMECONFLICT_UNDEFINED; import java.io.File; import java.io.IOException; import java.util.Date; import railo.commons.io.ModeUtil; import railo.commons.io.res.Resource; import railo.commons.io.res.ResourceMetaData; import railo.commons.io.res.filter.AndResourceFilter; import railo.commons.io.res.filter.DirectoryResourceFilter; import railo.commons.io.res.filter.FileResourceFilter; import railo.commons.io.res.filter.NotResourceFilter; import railo.commons.io.res.filter.OrResourceFilter; import railo.commons.io.res.filter.ResourceFilter; import railo.commons.io.res.filter.ResourceNameFilter; import railo.commons.io.res.type.file.FileResource; import railo.commons.io.res.type.s3.S3; import railo.commons.io.res.type.s3.S3Constants; import railo.commons.io.res.type.s3.S3Exception; import railo.commons.io.res.type.s3.S3Resource; import railo.commons.io.res.util.ModeObjectWrap; import railo.commons.io.res.util.ResourceAndResourceNameFilter; import railo.commons.io.res.util.ResourceUtil; import railo.commons.io.res.util.UDFFilter; import railo.commons.lang.StringUtil; import railo.runtime.PageContext; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.PageException; import railo.runtime.ext.tag.TagImpl; import railo.runtime.functions.s3.StoreSetACL; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.security.SecurityManager; import railo.runtime.tag.util.FileUtil; import railo.runtime.type.Array; import railo.runtime.type.ArrayImpl; import railo.runtime.type.Collection.Key; import railo.runtime.type.Query; import railo.runtime.type.QueryImpl; import railo.runtime.type.UDF; import railo.runtime.type.util.KeyConstants; /** * Handles interactions with directories. * * * **/ public final class Directory extends TagImpl { public static final int TYPE_ALL = 0; public static final int TYPE_FILE = 1; public static final int TYPE_DIR = 2; public static final ResourceFilter DIRECTORY_FILTER = new DirectoryResourceFilter(); public static final ResourceFilter FILE_FILTER = new FileResourceFilter(); private static final Key MODE = KeyConstants._mode; private static final Key META = KeyConstants._meta; private static final Key DATE_LAST_MODIFIED = KeyConstants._dateLastModified; private static final Key ATTRIBUTES = KeyConstants._attributes; private static final Key DIRECTORY = KeyConstants._directory; public static final int LIST_INFO_QUERY_ALL = 1; public static final int LIST_INFO_QUERY_NAME = 2; public static final int LIST_INFO_ARRAY_NAME = 4; public static final int LIST_INFO_ARRAY_PATH = 8; public static final int NAMECONFLICT_DEFAULT = NAMECONFLICT_OVERWRITE; // default /** Optional for action = "list". Ignored by all other actions. File extension filter applied to ** returned names. For example: *m. Only one mask filter can be applied at a time. */ private ResourceFilter filter; private ResourceAndResourceNameFilter nameFilter; /** The name of the directory to perform the action against. */ private Resource directory; /** Defines the action to be taken with directory(ies) specified in directory. */ private String action="list"; /** Optional for action = "list". Ignored by all other actions. The query columns by which to sort ** the directory listing. Any combination of columns from query output can be specified in comma-separated list. ** You can specify ASC (ascending) or DESC (descending) as qualifiers for column names. ASC is the default */ private String sort; /** Used with action = "Create" to define the permissions for a directory on UNIX and Linux ** platforms. Ignored on Windows. Options correspond to the octal values of the UNIX chmod command. From ** left to right, permissions are assigned for owner, group, and other. */ private int mode=-1; /** Required for action = "rename". Ignored by all other actions. The new name of the directory ** specified in the directory attribute. */ private String strNewdirectory; /** Required for action = "list". Ignored by all other actions. Name of output query for directory ** listing. */ private String name=null; private boolean recurse=false; private String serverPassword; private int type=TYPE_ALL; //private boolean listOnlyNames; private int listInfo=LIST_INFO_QUERY_ALL; //private int acl=S3Constants.ACL_UNKNOW; private Object acl=null; private int storage=S3Constants.STORAGE_UNKNOW; private String destination; private int nameconflict = NAMECONFLICT_DEFAULT; private boolean createPath=true; @Override public void release() { super.release(); acl=null; storage=S3Constants.STORAGE_UNKNOW; type=TYPE_ALL; filter=null; nameFilter=null; destination=null; directory=null; action="list"; sort=null; mode=-1; strNewdirectory=null; name=null; recurse=false; serverPassword=null; listInfo=LIST_INFO_QUERY_ALL; nameconflict = NAMECONFLICT_DEFAULT; createPath=true; } public void setCreatepath(boolean createPath) { this.createPath=createPath; } /** * sets a filter * @param filter * @throws PageException **/ public void setFilter(Object filter) throws PageException { this.filter=nameFilter=UDFFilter.createResourceAndResourceNameFilter(filter); } public void setFilter(UDF filter) throws PageException { this.filter=nameFilter=UDFFilter.createResourceAndResourceNameFilter(filter); } public void setFilter(String pattern) { this.filter = nameFilter = UDFFilter.createResourceAndResourceNameFilter( pattern ); } /** set the value acl * used only for s3 resources, for all others ignored * @param acl value to set * @throws ApplicationException * @Deprecated only exists for backward compatibility to old ra files. **/ public void setAcl(String acl) throws ApplicationException { this.acl=acl; /*acl=acl.trim().toLowerCase(); if("private".equals(acl)) this.acl=S3Constants.ACL_PRIVATE; else if("public-read".equals(acl)) this.acl=S3Constants.ACL_PRIVATE; else if("public-read-write".equals(acl)) this.acl=S3Constants.ACL_PUBLIC_READ_WRITE; else if("authenticated-read".equals(acl)) this.acl=S3Constants.ACL_AUTH_READ; else throw new ApplicationException("invalid value for attribute acl ["+acl+"]", "valid values are [private,public-read,public-read-write,authenticated-read]");*/ } public void setAcl(Object acl) { this.acl=acl; } public void setStoreacl(Object acl) { this.acl=acl; } /** set the value storage * used only for s3 resources, for all others ignored * @param storage value to set * @throws PageException **/ public void setStorage(String storage) throws PageException { try { this.storage=S3.toIntStorage(storage); } catch (S3Exception e) { throw Caster.toPageException(e); } } public void setStorelocation(String storage) throws PageException { setStorage(storage); } public void setServerpassword(String serverPassword) { this.serverPassword=serverPassword; } public void setListinfo(String strListinfo) { strListinfo=strListinfo.trim().toLowerCase(); this.listInfo="name".equals(strListinfo)?LIST_INFO_QUERY_NAME:LIST_INFO_QUERY_ALL; } /** set the value directory * The name of the directory to perform the action against. * @param directory value to set **/ public void setDirectory(String directory) { this.directory=ResourceUtil.toResourceNotExisting(pageContext, directory); //print.ln(this.directory); } /** set the value action * Defines the action to be taken with directory(ies) specified in directory. * @param action value to set **/ public void setAction(String action) { this.action=action.toLowerCase(); } /** set the value sort * Optional for action = "list". Ignored by all other actions. The query columns by which to sort * the directory listing. Any combination of columns from query output can be specified in comma-separated list. * You can specify ASC (ascending) or DESC (descending) as qualifiers for column names. ASC is the default * @param sort value to set **/ public void setSort(String sort) { if(sort.trim().length()>0) this.sort=sort; } /** set the value mode * Used with action = "Create" to define the permissions for a directory on UNIX and Linux * platforms. Ignored on Windows. Options correspond to the octal values of the UNIX chmod command. From * left to right, permissions are assigned for owner, group, and other. * @param mode value to set * @throws PageException **/ public void setMode(String mode) throws PageException { try { this.mode=ModeUtil.toOctalMode(mode); } catch (IOException e) { throw Caster.toPageException(e); } } /** set the value newdirectory * Required for action = "rename". Ignored by all other actions. The new name of the directory * specified in the directory attribute. * @param newdirectory value to set **/ public void setNewdirectory(String newdirectory) { //this.newdirectory=ResourceUtil.toResourceNotExisting(pageContext ,newdirectory); this.strNewdirectory=newdirectory; } public void setDestination(String destination) { this.destination=destination; } /** set the value name * Required for action = "list". Ignored by all other actions. Name of output query for directory * listing. * @param name value to set **/ public void setName(String name) { this.name=name; } /** * @param recurse The recurse to set. */ public void setRecurse(boolean recurse) { this.recurse = recurse; } /** set the value nameconflict * Action to take if destination directory is the same as that of a file in the directory. * @param nameconflict value to set * @throws ApplicationException **/ public void setNameconflict(String nameconflict) throws ApplicationException { this.nameconflict = FileUtil.toNameConflict( nameconflict, NAMECONFLICT_UNDEFINED | NAMECONFLICT_ERROR | NAMECONFLICT_OVERWRITE, NAMECONFLICT_DEFAULT ); } @Override public int doStartTag() throws PageException { //securityManager = pageContext.getConfig().getSecurityManager(); if(action.equals("list")) { Object res=actionList(pageContext,directory,serverPassword,type,filter,nameFilter,listInfo,recurse,sort); if(!StringUtil.isEmpty(name) && res!=null)pageContext.setVariable(name,res); } else if(action.equals("create")) actionCreate(pageContext,directory,serverPassword,createPath,mode,acl,storage, nameconflict); else if(action.equals("delete")) actionDelete(pageContext,directory,recurse,serverPassword); else if(action.equals("forcedelete")) actionDelete(pageContext,directory,true,serverPassword); else if(action.equals("rename")) actionRename(pageContext,directory,strNewdirectory,serverPassword,createPath,acl,storage); else if(action.equals("copy")) { if(StringUtil.isEmpty(destination,true) && !StringUtil.isEmpty(strNewdirectory,true)) { destination=strNewdirectory.trim(); } actionCopy(pageContext,directory,destination,serverPassword,createPath,acl,storage,filter,recurse, nameconflict); } else throw new ApplicationException("invalid action ["+action+"] for the tag directory"); return SKIP_BODY; } @Override public int doEndTag() { return EVAL_PAGE; } /** * list all files and directories inside a directory * @throws PageException */ public static Object actionList(PageContext pageContext,Resource directory, String serverPassword, int type,ResourceFilter filter,ResourceAndResourceNameFilter nameFilter, int listInfo,boolean recurse,String sort) throws PageException { // check directory SecurityManager securityManager = pageContext.getConfig().getSecurityManager(); securityManager.checkFileLocation(pageContext.getConfig(),directory,serverPassword); if(type!=TYPE_ALL) { ResourceFilter typeFilter = (type==TYPE_DIR)?DIRECTORY_FILTER:FILE_FILTER; if(filter==null) filter=typeFilter; else filter=new AndResourceFilter(new ResourceFilter[]{typeFilter,filter}); } // create query Object String[] names = new String[]{"name","size","type","dateLastModified","attributes","mode","directory"}; String[] types=new String[]{"VARCHAR","DOUBLE","VARCHAR","DATE","VARCHAR","VARCHAR","VARCHAR"}; boolean hasMeta=directory instanceof ResourceMetaData; if(hasMeta){ names = new String[]{"name","size","type","dateLastModified","attributes","mode","directory","meta"}; types=new String[]{"VARCHAR","DOUBLE","VARCHAR","DATE","VARCHAR","VARCHAR","VARCHAR","OBJECT"}; } Array array=null; Query query=null; Object rtn; if(listInfo==LIST_INFO_QUERY_ALL || listInfo==LIST_INFO_QUERY_NAME){ boolean listOnlyNames=listInfo==LIST_INFO_QUERY_NAME; rtn=query=new QueryImpl( listOnlyNames?new String[]{"name"}:names, listOnlyNames?new String[]{"VARCHAR"}:types, 0,"query"); } else rtn=array=new ArrayImpl(); if(!directory.exists()){ if(directory instanceof FileResource) return rtn; throw new ApplicationException("directory ["+directory.toString()+"] doesn't exist"); } if(!directory.isDirectory()){ if(directory instanceof FileResource) return rtn; throw new ApplicationException("file ["+directory.toString()+"] exists, but isn't a directory"); } if(!directory.isReadable()){ if(directory instanceof FileResource) return rtn; throw new ApplicationException("no access to read directory ["+directory.toString()+"]"); } long startNS=System.nanoTime(); try { // Query All if(listInfo==LIST_INFO_QUERY_ALL) _fillQueryAll(query,directory,filter,0,hasMeta,recurse); // Query Name else if(listInfo==LIST_INFO_QUERY_NAME) { if(recurse || type!=TYPE_ALL)_fillQueryNamesRec("",query, directory, filter, 0,recurse); else _fillQueryNames(query, directory, nameFilter, 0); } //Array Name/Path else if(listInfo==LIST_INFO_ARRAY_NAME || listInfo==LIST_INFO_ARRAY_PATH) { boolean onlyName=listInfo==LIST_INFO_ARRAY_NAME; if(!onlyName || recurse || type!=TYPE_ALL)_fillArrayPathOrName(array, directory, nameFilter, 0, recurse, onlyName);//QueryNamesRec("",query, directory, filter, 0,recurse); else _fillArrayName(array, directory, nameFilter, 0); } } catch (IOException e) { throw Caster.toPageException(e); } // sort if(sort!=null && query!=null) { String[] arr=sort.toLowerCase().split(","); for(int i=arr.length-1;i>=0;i--) { try { String[] col=arr[i].trim().split("\\s+"); if(col.length==1)query.sort(col[0].trim()); else if(col.length==2) { String order=col[1].toLowerCase().trim(); if(order.equals("asc")) query.sort(col[0],railo.runtime.type.Query.ORDER_ASC); else if(order.equals("desc")) query.sort(col[0],railo.runtime.type.Query.ORDER_DESC); else throw new ApplicationException("invalid order type ["+col[1]+"]"); } } catch(Throwable t) {} } } if(query!=null)query.setExecutionTime(System.nanoTime()-startNS); return rtn; } private static int _fillQueryAll(Query query, Resource directory, ResourceFilter filter, int count, boolean hasMeta, boolean recurse) throws PageException, IOException { //long start=System.currentTimeMillis(); Resource[] list=directory.listResources(); if(list==null || list.length==0) return count; String dir=directory.getCanonicalPath(); // fill data to query //query.addRow(list.length); boolean isDir; for(int i=0;i<list.length;i++) { if(filter==null || filter.accept(list[i])) { query.addRow(1); count++; query.setAt(KeyConstants._name,count,list[i].getName()); isDir=list[i].isDirectory(); query.setAt(KeyConstants._size,count,new Double(isDir?0:list[i].length())); query.setAt(KeyConstants._type,count,isDir?"Dir":"File"); if(directory.getResourceProvider().isModeSupported()){ query.setAt(MODE,count,new ModeObjectWrap(list[i])); } query.setAt(DATE_LAST_MODIFIED,count,new Date(list[i].lastModified())); query.setAt(ATTRIBUTES,count,getFileAttribute(list[i],true)); if(hasMeta){ query.setAt(META,count,((ResourceMetaData)list[i]).getMetaData()); } query.setAt(DIRECTORY,count,dir); } if(recurse && list[i].isDirectory()) count=_fillQueryAll(query,list[i],filter,count,hasMeta,recurse); } return count; } // this method only exists for performance reasion private static int _fillQueryNames(Query query, Resource directory, ResourceNameFilter filter, int count) throws PageException { String[] list=directory.list(); if(list==null || list.length==0) return count; for(int i=0;i<list.length;i++) { if(filter==null || filter.accept(directory,list[i])) { query.addRow(1); count++; query.setAt(KeyConstants._name,count,list[i]); } } return count; } private static int _fillQueryNamesRec(String parent, Query query, Resource directory, ResourceFilter filter, int count, boolean recurse) throws PageException { Resource[] list=directory.listResources(); if(list==null || list.length==0) return count; for(int i=0;i<list.length;i++) { if(filter==null || filter.accept(list[i])) { query.addRow(1); count++; query.setAt(KeyConstants._name,count,parent.concat(list[i].getName())); } if(recurse && list[i].isDirectory()) count=_fillQueryNamesRec(parent + list[i].getName() + "/", query, list[i], filter, count, recurse); } return count; } private static int _fillArrayPathOrName(Array arr, Resource directory, ResourceFilter filter, int count, boolean recurse,boolean onlyName) throws PageException { Resource[] list=directory.listResources(); if(list==null || list.length==0) return count; for(int i=0;i<list.length;i++) { if(filter==null || filter.accept(list[i])) { arr.appendEL(onlyName?list[i].getName():list[i].getAbsolutePath()); count++; } if(recurse && list[i].isDirectory()) count=_fillArrayPathOrName(arr,list[i],filter,count,recurse,onlyName); } return count; } // this method only exists for performance reasion private static int _fillArrayName(Array arr, Resource directory, ResourceNameFilter filter, int count) { String[] list=directory.list(); if(list==null || list.length==0) return count; for(int i=0;i<list.length;i++) { if(filter==null || filter.accept(directory,list[i])) { arr.appendEL(list[i]); } } return count; } /** * create a directory * @throws PageException */ public static void actionCreate(PageContext pc,Resource directory,String serverPassword, boolean createPath, int mode, Object acl, int storage, int nameConflict) throws PageException { SecurityManager securityManager = pc.getConfig().getSecurityManager(); securityManager.checkFileLocation(pc.getConfig(),directory,serverPassword); if(directory.exists()) { if(directory.isDirectory()) { if ( nameConflict == NAMECONFLICT_SKIP ) return; throw new ApplicationException("directory ["+directory.toString()+"] already exist"); } else if(directory.isFile()) throw new ApplicationException("can't create directory ["+directory.toString()+"], it exist a file with same name"); } //if(!directory.mkdirs()) throw new ApplicationException("can't create directory ["+directory.toString()+"]"); try { directory.createDirectory(createPath); } catch (IOException ioe) { throw Caster.toPageException(ioe); } // set S3 stuff setS3Attrs(directory,acl,storage); // Set Mode if(mode!=-1) { try { directory.setMode(mode); //FileUtil.setMode(directory,mode); } catch (IOException e) { throw Caster.toPageException(e); } } } private static void setS3Attrs(Resource res,Object acl,int storage) throws PageException { String scheme = res.getResourceProvider().getScheme(); if("s3".equalsIgnoreCase(scheme)){ S3Resource s3r=(S3Resource) res; if(acl!=null){ try { // old way if(Decision.isString(acl)) { if(Decision.isInteger(acl)) s3r.setACL(Caster.toIntValue(acl)); else s3r.setACL(S3.toIntACL(Caster.toString(acl))); } // new way else { StoreSetACL.invoke(s3r, acl); } } catch (IOException e) { throw Caster.toPageException(e); } } if(storage!=S3Constants.STORAGE_UNKNOW) s3r.setStorage(storage); } } /** * delete directory * @param dir * @param forceDelete * @throws PageException */ public static void actionDelete(PageContext pc,Resource dir, boolean forceDelete,String serverPassword) throws PageException { SecurityManager securityManager = pc.getConfig().getSecurityManager(); securityManager.checkFileLocation(pc.getConfig(),dir,serverPassword); // directory doesn't exist if(!dir.exists()) { if(dir.isDirectory()) throw new ApplicationException("directory ["+dir.toString()+"] doesn't exist"); else if(dir.isFile()) throw new ApplicationException("file ["+dir.toString()+"] doesn't exist and isn't a directory"); } // check if file if(dir.isFile()) throw new ApplicationException("can't delete ["+dir.toString()+"], it isn't a directory it is a file"); // delete directory try { dir.remove(forceDelete); } catch (IOException e) { throw Caster.toPageException(e); } } /** * rename a directory to a new Name * @throws PageException */ public static void actionRename(PageContext pc,Resource directory,String strNewdirectory,String serverPassword, boolean createPath, Object acl,int storage) throws PageException { // check directory SecurityManager securityManager = pc.getConfig().getSecurityManager(); securityManager.checkFileLocation(pc.getConfig(),directory,serverPassword); if(!directory.exists()) throw new ApplicationException("the directory ["+directory.toString()+"] doesn't exist"); if(!directory.isDirectory()) throw new ApplicationException("the file ["+directory.toString()+"] exists, but it isn't a directory"); if(!directory.canRead()) throw new ApplicationException("no access to read directory ["+directory.toString()+"]"); if(strNewdirectory==null) throw new ApplicationException("the attribute [newDirectory] is not defined"); // real to source Resource newdirectory=toDestination(pc,strNewdirectory,directory); securityManager.checkFileLocation(pc.getConfig(),newdirectory,serverPassword); if(newdirectory.exists()) throw new ApplicationException("new directory ["+newdirectory.toString()+"] already exists"); if(createPath) { newdirectory.getParentResource().mkdirs(); } try { directory.moveTo(newdirectory); } catch(Throwable t) { throw Caster.toPageException(t); } // set S3 stuff setS3Attrs(directory,acl,storage); } public static void actionCopy(PageContext pc,Resource directory,String strDestination,String serverPassword,boolean createPath, Object acl,int storage, ResourceFilter filter, boolean recurse, int nameconflict) throws PageException { // check directory SecurityManager securityManager = pc.getConfig().getSecurityManager(); securityManager.checkFileLocation(pc.getConfig(),directory,serverPassword); if(!directory.exists()) throw new ApplicationException("directory ["+directory.toString()+"] doesn't exist"); if(!directory.isDirectory()) throw new ApplicationException("file ["+directory.toString()+"] exists, but isn't a directory"); if(!directory.canRead()) throw new ApplicationException("no access to read directory ["+directory.toString()+"]"); if(StringUtil.isEmpty(strDestination)) throw new ApplicationException("attribute destination is not defined"); // real to source Resource newdirectory=toDestination(pc,strDestination,directory); if ( nameconflict == NAMECONFLICT_ERROR && newdirectory.exists() ) throw new ApplicationException("new directory ["+newdirectory.toString()+"] already exist"); securityManager.checkFileLocation(pc.getConfig(),newdirectory,serverPassword); try { // has already a filter if(filter!=null) { if(recurse) filter=new OrResourceFilter(new ResourceFilter[]{ filter,DirectoryResourceFilter.FILTER }); } else { if(!recurse)filter=new NotResourceFilter(DirectoryResourceFilter.FILTER); } if(!createPath) { Resource p = newdirectory.getParentResource(); if(p!=null && !p.exists()) throw new ApplicationException("parent directory for ["+newdirectory+"] doesn't exist"); } ResourceUtil.copyRecursive(directory, newdirectory,filter); } catch(Throwable t) { throw new ApplicationException(t.getMessage()); } // set S3 stuff setS3Attrs(directory,acl,storage); } private static Resource toDestination(PageContext pageContext,String path, Resource source) { if(source!=null && path.indexOf(File.separatorChar)==-1 && path.indexOf('/')==-1 && path.indexOf('\\')==-1) { Resource p = source.getParentResource(); if(p!=null)return p.getRealResource(path); } return ResourceUtil.toResourceNotExisting(pageContext ,path); } private static String getFileAttribute(Resource file, boolean exists){ return exists && !file.isWriteable() ? "R".concat(file.isHidden() ? "H" : "") : file.isHidden() ? "H" : ""; } /** * @param strType the type to set */ public void setType(String strType) throws ApplicationException { strType=strType.trim().toLowerCase(); if("all".equals(strType)) type=TYPE_ALL; else if("dir".equals(strType)) type=TYPE_DIR; else if("directory".equals(strType)) type=TYPE_DIR; else if("file".equals(strType)) type=TYPE_FILE; else throw new ApplicationException("invalid type ["+strType+"] for the tag directory"); } }