001    package railo.commons.io.res.util;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.PrintStream;
008    import java.util.ArrayList;
009    import java.util.HashMap;
010    
011    import net.sf.jmimemagic.Magic;
012    import net.sf.jmimemagic.MagicMatch;
013    import railo.commons.io.DevNullOutputStream;
014    import railo.commons.io.IOUtil;
015    import railo.commons.io.SystemUtil;
016    import railo.commons.io.res.ContentType;
017    import railo.commons.io.res.ContentTypeImpl;
018    import railo.commons.io.res.Resource;
019    import railo.commons.io.res.ResourcesImpl;
020    import railo.commons.io.res.filter.ExtensionResourceFilter;
021    import railo.commons.io.res.filter.ResourceFilter;
022    import railo.commons.io.res.filter.ResourceNameFilter;
023    import railo.commons.io.res.type.http.HTTPResource;
024    import railo.commons.lang.StringUtil;
025    import railo.runtime.PageContext;
026    import railo.runtime.PageContextImpl;
027    import railo.runtime.PageSource;
028    import railo.runtime.config.Config;
029    import railo.runtime.config.ConfigWebImpl;
030    import railo.runtime.exp.ExpressionException;
031    import railo.runtime.exp.PageException;
032    import railo.runtime.type.List;
033    import railo.runtime.type.util.ArrayUtil;
034    
035    public final class ResourceUtil {
036            
037            /**
038         * Field <code>FILE_SEPERATOR</code>
039         */
040        public static final char FILE_SEPERATOR=File.separatorChar; 
041        /**
042         * Field <code>FILE_ANTI_SEPERATOR</code>
043         */
044        public static final char FILE_ANTI_SEPERATOR=(FILE_SEPERATOR=='/')?'\\':'/';
045        
046        /**
047         * Field <code>TYPE_DIR</code>
048         */
049        public static final short TYPE_DIR=0;
050        
051        /**
052         * Field <code>TYPE_FILE</code>
053         */
054        public static final short TYPE_FILE=1;
055    
056        /**
057         * Field <code>LEVEL_FILE</code>
058         */
059        public static final short LEVEL_FILE=0;
060        /**
061         * Field <code>LEVEL_PARENT_FILE</code>
062         */
063        public static final short LEVEL_PARENT_FILE=1;
064        /**
065         * Field <code>LEVEL_GRAND_PARENT_FILE</code>
066         */
067        public static final short LEVEL_GRAND_PARENT_FILE=2;
068        
069        
070        private static boolean isUnix=SystemUtil.isUnix();
071        
072        private static final HashMap<String, String> EXT_MT=new HashMap<String, String>();
073        static {
074            EXT_MT.put("ai","application/postscript");
075            EXT_MT.put("aif","audio/x-aiff");
076            EXT_MT.put("aifc","audio/x-aiff");
077            EXT_MT.put("aiff","audio/x-aiff");
078            EXT_MT.put("au","audio/basic");
079            EXT_MT.put("avi","video/x-msvideo");
080            EXT_MT.put("bin","application/octet-stream");
081            EXT_MT.put("bmp","image/x-ms-bmp");
082            EXT_MT.put("cgm","image/cgm");
083            EXT_MT.put("cmx","image/x-cmx");
084            EXT_MT.put("csh","application/x-csh");
085            EXT_MT.put("css ","text/css");
086            EXT_MT.put("doc","application/msword");
087            EXT_MT.put("docx","application/msword");
088            EXT_MT.put("eps","application/postscript");
089            EXT_MT.put("exe","application/octet-stream");
090            EXT_MT.put("gif","image/gif");
091            EXT_MT.put("gtar","application/x-gtar");
092            EXT_MT.put("hqx","application/mac-binhex40");
093            EXT_MT.put("htm","text/html");
094            EXT_MT.put("html","text/html");
095            EXT_MT.put("jpe","image/jpeg");
096            EXT_MT.put("jpeg","image/jpeg");
097            EXT_MT.put("jpg","image/jpeg");
098            EXT_MT.put("js","text/javascript");
099            EXT_MT.put("mmid","x-music/x-midi");
100            EXT_MT.put("mov","video/quicktime");
101            EXT_MT.put("mp2a","audio/x-mpeg-2");
102            EXT_MT.put("mp2v","video/mpeg-2");
103            EXT_MT.put("mp3","audio/mpeg");
104            EXT_MT.put("mp4","video/mp4");
105            EXT_MT.put("mpa","audio/x-mpeg");
106            EXT_MT.put("mpa2","audio/x-mpeg-2");
107            EXT_MT.put("mpeg","video/mpeg");
108            EXT_MT.put("mpega","audio/x-mpeg");
109            EXT_MT.put("mpg","video/mpeg");
110            EXT_MT.put("mpv2","video/mpeg-2");
111            EXT_MT.put("pbm","image/x-portable-bitmap");
112            EXT_MT.put("pcd","image/x-photo-cd");
113            EXT_MT.put("pdf","application/pdf");
114            EXT_MT.put("pgm","image/x-portable-graymap");
115            EXT_MT.put("pict","image/x-pict");
116            EXT_MT.put("pl","application/x-perl");
117            EXT_MT.put("png","image/png");
118            EXT_MT.put("pnm","image/x-portable-anymap");
119            EXT_MT.put("ppm","image/x-portable-pixmap");
120            EXT_MT.put("ppt","application/vnd.ms-powerpoint");
121            EXT_MT.put("pptx","application/vnd.ms-powerpoint");
122            EXT_MT.put("ps","application/postscript");
123            EXT_MT.put("qt","video/quicktime");
124            EXT_MT.put("rgb","image/rgb");
125            EXT_MT.put("rtf","application/rtf");
126            EXT_MT.put("sh","application/x-sh");
127            EXT_MT.put("sit","application/x-stuffit");
128            EXT_MT.put("swf","application/x-shockwave-flash");
129            EXT_MT.put("tar","application/x-tar");
130            EXT_MT.put("tcl","application/x-tcl");
131            EXT_MT.put("tif","image/tiff");
132            EXT_MT.put("tiff","image/tiff");
133            EXT_MT.put("txt","text/plain");
134            EXT_MT.put("wav","audio/x-wav");
135            EXT_MT.put("wma","audio/x-ms-wma");
136            EXT_MT.put("wmv","video/x-ms-wmv");
137            EXT_MT.put("xbm","image/x-xbitmap");
138            EXT_MT.put("xhtml","application/xhtml+xml");
139            EXT_MT.put("xls","application/vnd.ms-excel");
140            EXT_MT.put("xlsx","application/vnd.ms-excel");
141            EXT_MT.put("xpm","image/x-xpixmap");
142            EXT_MT.put("zip","application/zip");
143            
144        }
145            
146    
147        //private static Magic mimeTypeParser; 
148            
149        /**
150         * cast a String (argumet destination) to a File Object, 
151         * if destination is not a absolute, file object will be relative to current position (get from PageContext)
152         * file must exist otherwise throw exception
153         * @param pc Page Context to et actuell position in filesystem
154         * @param path relative or absolute path for file object
155         * @return file object from destination
156         * @throws ExpressionException
157         */
158        public static Resource toResourceExisting(PageContext pc ,String path) throws ExpressionException {
159            return toResourceExisting(pc, path,pc.getConfig().allowRealPath());
160        }
161        public static Resource toResourceExisting(PageContext pc ,String path,boolean allowRealpath) throws ExpressionException {
162            path=path.replace('\\','/');
163            Resource res = pc.getConfig().getResource(path);
164            
165            // not allow realpath
166            if(!allowRealpath){
167                    if(res.exists()) return res;
168                    throw new ExpressionException("file or directory "+path+" not exist");  
169            }
170            
171            if(res.isAbsolute() && res.exists()) {
172                return res;
173            }
174            
175            //if(allowRealpath){
176                    if(StringUtil.startsWith(path,'/')) {
177                            PageContextImpl pci=(PageContextImpl) pc;
178                            ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
179                            Resource[] reses = cwi.getPhysicalResources(pc,pc.getApplicationContext().getMappings(),path,false,pci.useSpecialMappings(),true);
180                            if(!ArrayUtil.isEmpty(reses)) {
181                                    for(int i=0;i<reses.length;i++){
182                                            res=reses[i];
183                                            if(res.exists()) return res;
184                                    }
185                            }
186                            //res = pc.getPhysical(path,true);
187                        //if(res!=null && res.exists()) return res;
188                    }
189                    res=ResourceUtil.getCanonicalResourceEL(pc.getCurrentPageSource().getPhyscalFile().getParentResource().getRealResource(path));
190                    if(res.exists()) return res;
191            //}
192            
193            throw new ExpressionException("file or directory "+path+" not exist");      
194        }
195        
196        public static Resource toResourceExisting(Config config ,String path) throws ExpressionException {
197            path=path.replace('\\','/');
198            Resource res = config.getResource(path);
199            
200            if(res.exists()) return res;
201            throw new ExpressionException("file or directory "+path+" not exist");   
202        }
203        
204        public static Resource toResourceNotExisting(Config config ,String path) {
205            Resource res;
206            path=path.replace('\\','/');  
207            res=config.getResource(path);
208            return res;
209        }
210        
211        
212    
213        /**
214         * cast a String (argumet destination) to a File Object, 
215         * if destination is not a absolute, file object will be relative to current position (get from PageContext)
216         * at least parent must exist
217         * @param pc Page Context to et actuell position in filesystem
218         * @param destination relative or absolute path for file object
219         * @return file object from destination
220         * @throws ExpressionException
221         */
222    
223        public static Resource toResourceExistingParent(PageContext pc ,String destination) throws ExpressionException {
224            return toResourceExistingParent(pc, destination, pc.getConfig().allowRealPath());
225        }
226        
227        public static Resource toResourceExistingParent(PageContext pc ,String destination, boolean allowRealpath) throws ExpressionException {
228            destination=destination.replace('\\','/');
229            Resource res=pc.getConfig().getResource(destination);
230            
231            // not allow realpath
232            if(!allowRealpath){
233                    if(res.exists() || parentExists(res))
234                            return res;
235                    throw new ExpressionException("parent directory "+res.getParent()+"  for file "+destination+" doesn't exist");
236                
237            }
238            
239            // allow realpath
240            if(res.isAbsolute() && (res.exists() || parentExists(res))) {
241                    return res;
242            }
243            //if(allowRealpath){
244                    if(StringUtil.startsWith(destination,'/')) {
245                            PageContextImpl pci=(PageContextImpl) pc;
246                            ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
247                            Resource[] reses = cwi.getPhysicalResources(pc,pc.getApplicationContext().getMappings(),destination,false,pci.useSpecialMappings(),true);
248                            if(!ArrayUtil.isEmpty(reses)) {
249                                    for(int i=0;i<reses.length;i++){
250                                            res=reses[i];
251                                            if(res.exists() || parentExists(res)) return res;
252                                    }
253                            }
254                        //res = pc.getPhysical(destination,true);
255                        //if(res!=null && (res.exists() || parentExists(res))) return res;
256                    }
257                    res=ResourceUtil.getCanonicalResourceEL(pc.getCurrentPageSource().getPhyscalFile().getParentResource().getRealResource(destination));
258                    if(res!=null && (res.exists() || parentExists(res))) return res;
259            //}
260        
261            throw new ExpressionException("parent directory "+res.getParent()+"  for file "+destination+" doesn't exist");
262               
263        }
264        
265        /**
266         * cast a String (argument destination) to a File Object, 
267         * if destination is not a absolute, file object will be relative to current position (get from PageContext)
268         * existing file is prefered but dont must exist
269         * @param pc Page Context to et actuell position in filesystem
270         * @param destination relative or absolute path for file object
271         * @return file object from destination
272         */
273    
274        public static Resource toResourceNotExisting(PageContext pc ,String destination) {
275            return toResourceNotExisting(pc ,destination,pc.getConfig().allowRealPath());
276        }
277        
278        public static Resource toResourceNotExisting(PageContext pc ,String destination,boolean allowRealpath) {
279            Resource res;
280            destination=destination.replace('\\','/');  
281            
282            if(!allowRealpath){
283                    res=pc.getConfig().getResource(destination);
284                    return res;
285            }
286            
287            boolean isUNC;
288            if(!(isUNC=isUNCPath(destination)) && StringUtil.startsWith(destination,'/')) {
289                    PageContextImpl pci=(PageContextImpl) pc;
290                    ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
291                    Resource[] arr = cwi.getPhysicalResources(pc,pc.getApplicationContext().getMappings(),destination,false,pci.useSpecialMappings(),SystemUtil.isWindows());
292                    if(!ArrayUtil.isEmpty(arr)) return arr[0];
293                    //Resource res2 = pc.getPhysical(destination,SystemUtil.isWindows());
294                //if(res2!=null) return res2;
295            }
296            if(isUNC) {
297                    res=pc.getConfig().getResource(destination.replace('/','\\'));
298            }
299            else res=pc.getConfig().getResource(destination);
300            if(res.isAbsolute()) return res;
301            
302            
303            try {
304                    return pc.getCurrentPageSource().getPhyscalFile().getParentResource().getRealResource(destination).getCanonicalResource();
305            } 
306            catch (IOException e) {}
307            return res;
308        }
309        
310            
311    
312        private static boolean isUNCPath(String path) {
313            return SystemUtil.isWindows() && path.startsWith("//") ;
314            }
315        
316        /**
317         * transalte the path of the file to a existing file path by changing case of letters
318         * Works only on Linux, becasue 
319         * 
320         * Example Unix:
321         * we have a existing file with path "/usr/virtual/myFile.txt"
322         * now you call this method with path "/Usr/Virtual/myfile.txt"
323         * the result of the method will be "/usr/virtual/myFile.txt"
324         * 
325         * if there are more file with rhe same name but different cases
326         * Example:
327         *  /usr/virtual/myFile.txt
328         *  /usr/virtual/myfile.txt
329         *  /Usr/Virtual/myFile.txt
330         *  the nearest case wil returned
331         * 
332         * @param res
333         * @return file
334         */
335        public static Resource toExactResource(Resource res) {
336            res=getCanonicalResourceEL(res);
337            if(isUnix) {
338                if(res.exists()) return res;
339                return _check(res);
340                
341            }
342            return res;
343        }
344        private static Resource _check(Resource file) {
345            // todo cascade durch while ersetzten
346            Resource parent=file.getParentResource();
347            if(parent==null) return file;
348            
349            if(!parent.exists()) {
350                Resource op=parent;
351                parent=_check(parent);
352                if(op==parent) return file;
353                if((file = parent.getRealResource(file.getName())).exists()) return file;
354            }
355            
356            String[] files = parent.list();
357            if(files==null) return file;
358            String name=file.getName();
359            for(int i=0;i<files.length;i++) {
360                if(name.equalsIgnoreCase(files[i]))
361                    return parent.getRealResource(files[i]);
362            }
363            return file;
364        }
365        
366        /**
367         * create a file if possible, return file if ok, otherwise return null 
368         * @param res file to touch 
369         * @param level touch also parent and grand parent
370         * @param type is file or directory
371         * @return file if exists, otherwise null
372         */
373        public static Resource createResource(Resource res, short level, short type) {
374            
375            boolean asDir=type==TYPE_DIR;
376            // File
377            if(level>=LEVEL_FILE && res.exists() && ((res.isDirectory() && asDir)||(res.isFile() && !asDir))) {
378                return getCanonicalResourceEL(res);
379            }
380            
381            // Parent
382            Resource parent=res.getParentResource();
383            if(level>=LEVEL_PARENT_FILE && parent!=null && parent.exists() && canRW(parent)) {
384                if(asDir) {
385                    if(res.mkdirs()) return getCanonicalResourceEL(res);
386                }
387                else {
388                    if(createNewResourceEL(res))return getCanonicalResourceEL(res);
389                }
390                return getCanonicalResourceEL(res);
391            }    
392            
393            // Grand Parent
394            if(level>=LEVEL_GRAND_PARENT_FILE && parent!=null) {
395                Resource gparent=parent.getParentResource();
396                if(gparent!=null && gparent.exists() && canRW(gparent)) {
397                    if(asDir) {
398                        if(res.mkdirs())return getCanonicalResourceEL(res);
399                    }
400                    else {
401                        if(parent.mkdirs() && createNewResourceEL(res))
402                            return getCanonicalResourceEL(res);
403                    }
404                }        
405            }
406            return null;
407        }
408        
409            public static void setAttribute(Resource res,String attributes) throws IOException {
410                    /*if(res instanceof File && SystemUtil.isWindows()) {
411                            if(attributes.length()>0) {
412                                    attributes=ResourceUtil.translateAttribute(attributes);
413                                    Runtime.getRuntime().exec("attrib "+attributes+" " + res.getAbsolutePath());
414                    }
415                    }
416                    else {*/
417                            short[] flags = strAttrToBooleanFlags(attributes);
418                            
419                            if(flags[READ_ONLY]==YES)res.setWritable(false);
420                            else if(flags[READ_ONLY]==NO)res.setWritable(true);
421                            
422                            if(flags[HIDDEN]==YES)          res.setAttribute(Resource.ATTRIBUTE_HIDDEN, true);//setHidden(true);
423                            else if(flags[HIDDEN]==NO)      res.setAttribute(Resource.ATTRIBUTE_HIDDEN, false);//res.setHidden(false);
424                            
425                            if(flags[ARCHIVE]==YES)         res.setAttribute(Resource.ATTRIBUTE_ARCHIVE, true);//res.setArchive(true);
426                            else if(flags[ARCHIVE]==NO)     res.setAttribute(Resource.ATTRIBUTE_ARCHIVE, false);//res.setArchive(false);
427                            
428                            if(flags[SYSTEM]==YES)          res.setAttribute(Resource.ATTRIBUTE_SYSTEM, true);//res.setSystem(true);
429                            else if(flags[SYSTEM]==NO)      res.setAttribute(Resource.ATTRIBUTE_SYSTEM, false);//res.setSystem(false);
430                            
431                    //}
432            }
433    
434            //private static final int NORMAL=0;
435            private static final int READ_ONLY=0;
436            private static final int HIDDEN=1;
437            private static final int ARCHIVE=2;
438            private static final int SYSTEM=3;
439    
440            //private static final int IGNORE=0;
441            private static final int NO=1;
442            private static final int YES=2;
443            
444            
445    
446        private static short[] strAttrToBooleanFlags(String attributes) throws IOException {
447            
448            String[] arr;
449                    try {
450                            arr = List.toStringArray(List.listToArrayRemoveEmpty(attributes.toLowerCase(),','));
451                    } 
452                    catch (PageException e) {
453                            arr=new String[0];
454                    }
455            
456            boolean hasNormal=false;
457            boolean hasReadOnly=false;
458            boolean hasHidden=false;
459            boolean hasArchive=false;
460            boolean hasSystem=false;
461            
462            for(int i=0;i<arr.length;i++) {
463               String str=arr[i].trim().toLowerCase();
464               if(str.equals("readonly") || str.equals("read-only") || str.equals("+r")) hasReadOnly=true;
465               else if(str.equals("normal") || str.equals("temporary")) hasNormal=true;
466               else if(str.equals("hidden") || str.equals("+h")) hasHidden=true;
467               else if(str.equals("system") || str.equals("+s")) hasSystem=true;
468               else if(str.equals("archive") || str.equals("+a")) hasArchive=true;
469               else throw new IOException("invalid attribute definition ["+str+"]");
470            }
471            
472            short[] flags=new short[4];
473            
474            if(hasReadOnly)flags[READ_ONLY]=YES;
475            else if(hasNormal)flags[READ_ONLY]=NO;
476            
477            if(hasHidden)flags[HIDDEN]=YES;
478            else if(hasNormal)flags[HIDDEN]=NO;
479            
480            if(hasSystem)flags[SYSTEM]=YES;
481            else if(hasNormal)flags[SYSTEM]=NO;
482            
483            if(hasArchive)flags[ARCHIVE]=YES;
484            else if(hasNormal)flags[ARCHIVE]=NO;
485            
486            return flags;
487        }
488            
489            
490            /**
491         * sets attributes of a file on Windows system
492         * @param res
493         * @param attributes
494         * @throws PageException
495         * @throws IOException
496         */
497        public static String translateAttribute(String attributes) throws IOException {
498            short[] flags = strAttrToBooleanFlags(attributes);
499           
500            StringBuilder sb=new StringBuilder();
501            if(flags[READ_ONLY]==YES)sb.append(" +R");
502            else if(flags[READ_ONLY]==NO)sb.append(" -R");
503            
504            if(flags[HIDDEN]==YES)sb.append(" +H");
505            else if(flags[HIDDEN]==NO)sb.append(" -H");
506            
507            if(flags[SYSTEM]==YES)sb.append(" +S");
508            else if(flags[SYSTEM]==NO)sb.append(" -S");
509            
510            if(flags[ARCHIVE]==YES)sb.append(" +A");
511            else if(flags[ARCHIVE]==NO)sb.append(" -A");
512    
513            return sb.toString();
514        }
515    
516            /* *
517             * transalte a path in a proper form
518             * example susi\petere -> /susi/peter
519             * @param path
520             * @return path
521             * /
522            public static String translatePath(String path) {
523                    /*path=prettifyPath(path);
524                    if(path.indexOf('/')!=0)path='/'+path;
525                    int index=path.lastIndexOf('/');
526                    // remove slash at the end
527                    if(index==path.length()-1) path=path.substring(0,path.length()-1);
528                    return path;* /
529                    return translatePath(path, true, false);
530            }*/
531            
532            /* *
533             * transalte a path in a proper form
534             * example susi\petere -> susi/peter/
535             * @param path
536             * @return path
537             * /
538            public static String translatePath2x(String path) {
539                    /*path=prettifyPath(path);
540                    if(path.indexOf('/')==0)path=path.substring(1);
541                    int index=path.lastIndexOf('/');
542                    // remove slash at the end
543                    if(index!=path.length()-1) path=path+'/';* /
544                    return translatePath(path, false, true);
545            }*/
546            
547    
548            public static String translatePath(String path, boolean slashAdBegin, boolean slashAddEnd) {
549                    path=prettifyPath(path);
550                    
551                    // begin
552                    if(slashAdBegin) {
553                            if(path.indexOf('/')!=0)path='/'+path;
554                    }
555                    else {
556                            if(path.indexOf('/')==0)path=path.substring(1);
557                    }
558                    
559                    // end
560                    int index=path.lastIndexOf('/');
561                    if(slashAddEnd) {
562                            if(index!=path.length()-1) path=path+'/';
563                    }
564                    else {
565                            if(index==path.length()-1 && index>-1) path=path.substring(0,path.length()-1);
566                    }
567                    return path;
568            }
569            
570            
571            
572    
573            /**
574             * transalte a path in a proper form and cut name away
575             * example susi\petere -> /susi/ and  peter
576             * @param path
577             * @return
578             */
579            public static String[] translatePathName(String path) {
580                    path=prettifyPath(path);
581                    if(path.indexOf('/')!=0)path='/'+path;
582                    int index=path.lastIndexOf('/');
583                    // remove slash at the end
584                    if(index==path.length()-1) path=path.substring(0,path.length()-1);
585                    
586                    index=path.lastIndexOf('/');
587                    String name;
588                    if(index==-1) {
589                            name=path;
590                            path = "/";
591                    }
592                    else {
593                            name = path.substring(index+1);
594                            path = path.substring(0,index+1);
595                    }
596                    return new String[] {path,name};
597            }
598            
599            public static String prettifyPath(String path) {
600                    path=path.replace('\\','/');
601                    return StringUtil.replace(path, "//", "/", false);
602                    // TODO /aaa/../bbb/
603            }
604    
605            public static String removeScheme(String scheme, String path) {
606                    if(path.indexOf("://")==scheme.length() && StringUtil.startsWithIgnoreCase(path,scheme)) path=path.substring(3+scheme.length());
607                    return path;
608            }
609    
610            /**
611             * merge to path parts to one
612             * @param parent
613             * @param child
614             * @return
615             */
616            public static String merge(String parent, String child) {
617                    if(child.length()<=2) {
618                            if(child.length()==0) return parent;
619                            if(child.equals(".")) return parent;
620                            if(child.equals("..")) child="../";
621                    }
622                    
623                    
624                    
625                    parent=translatePath(parent, true, false);
626                    child=prettifyPath(child);//child.replace('\\', '/');
627                    
628                    if(child.startsWith("./"))child=child.substring(2);
629                    if(StringUtil.startsWith(child, '/'))return parent.concat(child);
630                    if(!StringUtil.startsWith(child, '.'))return parent.concat("/").concat(child);
631                    
632                    
633                    while(child.startsWith("../")) {
634                            parent=pathRemoveLast(parent);
635                            child=child.substring(3);
636                    }
637                    if(StringUtil.startsWith(child, '/'))return parent.concat(child);
638                    return parent.concat("/").concat(child);
639            }
640            
641            private static String pathRemoveLast(String path) {
642                    if(path.length()==0) return "..";
643                    
644                    else if(path.endsWith("..")){
645                        return path.concat("/..");
646                    }
647                    return path.substring(0,path.lastIndexOf('/'));
648            }
649    
650            /**
651         * Returns the canonical form of this abstract pathname.
652         * @param res file to get canoncial form from it
653         *
654         * @return  The canonical pathname string denoting the same file or
655         *          directory as this abstract pathname
656         *
657         * @throws  SecurityException
658         *          If a required system property value cannot be accessed.
659         */
660        public static String getCanonicalPathEL(Resource res) {
661            try {
662                return res.getCanonicalPath();
663            } catch (IOException e) {
664                return res.toString();
665            }
666        }
667        
668        
669        /**
670         * Returns the canonical form of this abstract pathname.
671         * @param res file to get canoncial form from it
672         *
673         * @return  The canonical pathname string denoting the same file or
674         *          directory as this abstract pathname
675         *
676         * @throws  SecurityException
677         *          If a required system property value cannot be accessed.
678         */
679        public static Resource getCanonicalResourceEL(Resource res) {
680            if(res==null) return res;
681            try {
682                return res.getCanonicalResource();
683            } catch (IOException e) {
684                return res;
685            }
686        }
687        
688        /**
689         * creates a new File
690         * @param res
691         * @return was successfull
692         */
693        public static boolean createNewResourceEL(Resource res) {
694            try {
695                res.createFile(false);
696                return true;
697            } catch (IOException e) {
698                return false;
699            }
700        }
701    
702        public static boolean exists(Resource res) {
703            return res!=null && res.exists();
704        }
705        
706    
707        /**
708         * check if file is read and writable
709         * @param res
710         * @return is or not
711         */
712        public static boolean canRW(Resource res) {
713            return res.isReadable() && res.isWriteable();
714        }
715        
716    
717        /**
718         * similat to linux bash fuction toch, create file if not exists oherwise change last modified date
719         * @param res
720         * @throws IOException
721         */
722        public static void touch(Resource res) throws IOException {
723            if(res.exists()) {
724                    res.setLastModified(System.currentTimeMillis());
725                }
726                else {
727                    res.createFile(true);
728                }
729        }
730        
731        public static void clear(Resource res) throws IOException {
732            if(res.exists()) {
733                    IOUtil.write(res, new byte[0]);
734                }
735                else {
736                    res.createFile(true);
737                }
738        }
739            
740        
741    
742        /**
743         * return the mime type of a file, dont check extension
744         * @param res
745         * @param defaultValue 
746         * @return mime type of the file
747         */
748        public static String getMymeType(Resource res, String defaultValue) {
749            return getMymeType(res, false,defaultValue);
750        }
751        
752        public static String getMymeType(Resource res, boolean alsoCheckExtension, String defaultValue) {
753            if(alsoCheckExtension) {
754                    String ext = getExtension(res, null);
755                            if(!StringUtil.isEmpty(ext)){
756                            String mt=EXT_MT.get(ext.trim().toLowerCase());
757                            if(mt!=null) return mt;
758                            }
759            }
760            PrintStream out = System.out;
761            try {
762                    System.setOut(new PrintStream(DevNullOutputStream.DEV_NULL_OUTPUT_STREAM));
763                MagicMatch match = Magic.getMagicMatch(IOUtil.toBytes(res));
764                return match.getMimeType();
765            } 
766            catch (Exception e) {
767                return defaultValue;
768            }
769            finally {
770                    System.setOut(out);
771            }
772        }
773    
774        /**
775         * return the mime type of a file, dont check extension
776         * @param barr
777         * @return mime type of the file
778         * @throws IOException 
779         */
780        public static String getMymeType(byte[] barr) throws IOException {
781            //if(mimeTypeParser==null)mimeTypeParser=new Magic();
782            PrintStream out = System.out;
783            try {
784                    System.setOut(new PrintStream(DevNullOutputStream.DEV_NULL_OUTPUT_STREAM));
785                MagicMatch match = Magic.getMagicMatch(barr);
786                return match.getMimeType();
787            } 
788            catch (Exception e) {
789                throw new IOException(e.getMessage());
790            }
791            finally {
792                    System.setOut(out);
793            }
794        }
795    
796        /**
797         * return the mime type of a file, dont check extension
798         * @param barr
799         * @param defaultValue 
800         * @return mime type of the file
801         */
802        public static String getMymeType(byte[] barr, String defaultValue) {
803            PrintStream out = System.out;
804            try {
805                    System.setOut(new PrintStream(DevNullOutputStream.DEV_NULL_OUTPUT_STREAM));
806                MagicMatch match = Magic.getMagicMatch(barr);
807                return match.getMimeType();
808            } 
809            catch (Exception e) {
810                return defaultValue;
811            }
812            finally {
813                    System.setOut(out);
814            }
815        }
816        
817            /**
818             * check if file is a child of given directory
819             * @param file file to search
820             * @param dir directory to search
821             * @return is inside or not
822             */
823            public static boolean isChildOf(Resource file, Resource dir) {
824                if(file==null) return false;
825                if(file.equals(dir)) return true;
826                return isChildOf(file.getParentResource(),dir);
827            }
828            /**
829             * return diffrents of one file to a other if first is child of second otherwise return null
830             * @param file file to search
831             * @param dir directory to search
832             */
833            public static String getPathToChild(Resource file, Resource dir) {
834                    boolean isFile=file.isFile();
835                    String str="/";
836                    while(file!=null) {
837                            //print.out("- "+file+".equals("+dir+"):"+file.equals(dir));
838                            if(file.equals(dir)) {
839                                    if(isFile)
840                                            return str.substring(0,str.length()-1);
841                                    return str;
842                            }
843                            str="/"+file.getName()+str;
844                            file=file.getParentResource();
845                    }
846                    return null;
847            }
848            
849        /**
850         * get the Extension of a file
851         * @param res
852         * @return extension of file
853         */
854        public static String getExtension(Resource res, String defaultValue) {
855            return getExtension(res.getName(),defaultValue);
856        }
857    
858        /**
859         * get the Extension of a file
860         * @param strFile
861         * @return extension of file
862         */
863        public static String getExtension(String strFile, String defaultValue) {
864            int pos=strFile.lastIndexOf('.');
865            if(pos==-1)return defaultValue;
866            return strFile.substring(pos+1);
867        }
868        
869        public static String getName(String strFileName) {
870            int pos=strFileName.lastIndexOf('.');
871            if(pos==-1)return strFileName;
872            return strFileName.substring(0,pos);
873        }
874        
875        /**
876         * split a FileName in Parts
877         * @param fileName
878         * @return new String[]{name[,extension]}
879         */
880        public static String[] splitFileName(String fileName) {
881            int pos=fileName.lastIndexOf('.');
882            if(pos==-1) {
883                return new String[]{fileName};
884            }
885            return new String[]{fileName.substring(0,pos),fileName.substring(pos+1)};
886        }
887        
888        /**
889         * change extesnion of file and return new file
890         * @param file
891         * @param newExtension
892         * @return  file with new Extension
893         */
894        public static Resource changeExtension(Resource file, String newExtension) {
895            String ext=getExtension(file,null);
896            if(ext==null) return file.getParentResource().getRealResource(file.getName()+'.'+newExtension);
897            //new File(file.getParentFile(),file.getName()+'.'+newExtension);
898            String name=file.getName();
899            return file.getParentResource().getRealResource(name.substring(0,name.length()-ext.length())+newExtension);
900            //new File(file.getParentFile(),name.substring(0,name.length()-ext.length())+newExtension);
901        }
902        
903        /**
904         * @param res delete the content of a directory
905         */
906    
907        public static void deleteContent(Resource src,ResourceFilter filter) {
908            _deleteContent(src, filter,false);
909        }
910        public static void _deleteContent(Resource src,ResourceFilter filter,boolean deleteDirectories) {
911            if(src.isDirectory()) {
912                    Resource[] files=filter==null?src.listResources():src.listResources(filter);
913                for(int i=0;i<files.length;i++) {
914                    _deleteContent(files[i],filter,true);
915                    if(deleteDirectories){
916                            try {
917                                                    src.remove(false);
918                                            } catch (IOException e) {}
919                    }
920                }
921                
922            }
923            else if(src.isFile()) {
924                    src.delete();
925            }
926        }
927        
928    
929        /**
930         * copy a file or directory recursive (with his content)
931         * @param res file or directory to delete
932         * @throws IOException 
933         * @throws FileNotFoundException 
934         */
935        public static void copyRecursive(Resource src,Resource trg) throws IOException {
936                    copyRecursive(src, trg,null);
937            }
938        
939        
940        /**
941         * copy a file or directory recursive (with his content)
942         * @param src
943         * @param trg
944         * @param filter
945         * @throws IOException 
946         * @throws FileNotFoundException 
947         */
948        public static void copyRecursive(Resource src,Resource trg,ResourceFilter filter) throws IOException {
949            //print.out(src);
950            //print.out(trg);
951            if(!src.exists()) return ;
952            if(src.isDirectory()) {
953                    if(!trg.exists())trg.createDirectory(true);
954                    Resource[] files=filter==null?src.listResources():src.listResources(filter);
955                for(int i=0;i<files.length;i++) {
956                    copyRecursive(files[i],trg.getRealResource(files[i].getName()),filter);
957                }
958            }
959            else if(src.isFile()) {
960                    touch(trg);
961                    IOUtil.copy(src,trg);
962            }
963        }
964        
965            public static void copy(Resource src, Resource trg) throws IOException {
966                    if(src.equals(trg)) return;
967                    ResourceUtil.checkCopyToOK(src,trg);
968                    IOUtil.copy(src,trg);
969            }
970        
971        
972        /**
973         * return if parent file exists 
974         * @param res file to check
975         * @return parent exists?
976         */
977        private static boolean parentExists(Resource res) {
978            res=res.getParentResource();
979            return res!=null && res.exists();
980        }
981    
982            public static void removeChildren(Resource res) throws IOException {
983                    removeChildren(res, (ResourceFilter)null);
984            }
985    
986            public static void removeChildren(Resource res,ResourceNameFilter filter) throws IOException {
987                    Resource[] children = filter==null?res.listResources():res.listResources(filter);
988                    if(children==null) return;
989                    
990                    for(int i=0;i<children.length;i++) {
991                            children[i].remove(true);
992                    }
993            }
994            
995            public static void removeChildren(Resource res,ResourceFilter filter) throws IOException {
996                    Resource[] children = filter==null?res.listResources():res.listResources(filter);
997                    if(children==null) return;
998                    
999                    for(int i=0;i<children.length;i++) {
1000                            children[i].remove(true);
1001                    }
1002            }
1003    
1004            public static void removeChildrenEL(Resource res,ResourceNameFilter filter) {
1005                    try {
1006                            removeChildren(res,filter);
1007                    }
1008                    catch(Throwable e) {}
1009            }
1010    
1011            public static void removeChildrenEL(Resource res,ResourceFilter filter) {
1012                    try {
1013                            removeChildren(res,filter);
1014                    }
1015                    catch(Throwable e) {}
1016            }
1017            
1018            public static void removeChildrenEL(Resource res) {
1019                    try {
1020                            removeChildren(res);
1021                    }
1022                    catch(Throwable e) {}
1023            }
1024    
1025            public static void removeEL(Resource res, boolean force) {
1026                    try {
1027                            res.remove(force);
1028                    } 
1029                    catch (Throwable t) {}
1030            }
1031    
1032            public static void createFileEL(Resource res, boolean force) {
1033                    try {
1034                            res.createFile(force);
1035                    } 
1036                    catch (IOException e) {}
1037            }
1038    
1039            public static void createDirectoryEL(Resource res, boolean force) {
1040                    try {
1041                            res.createDirectory(force);
1042                    } 
1043                    catch (IOException e) {}
1044            }
1045    
1046            public static ContentType getContentType(Resource resource) {
1047                    // TODO make this part of a interface
1048                    if(resource instanceof HTTPResource) {
1049                            try {
1050                                    return ((HTTPResource)resource).getContentType();
1051                            } catch (IOException e) {}
1052                    }
1053                    InputStream is=null;
1054                    try {
1055                            is = resource.getInputStream();
1056                            return new ContentTypeImpl(is);
1057                    }
1058                    catch(IOException e) {
1059                            return ContentTypeImpl.APPLICATION_UNKNOW;
1060                    }
1061                    finally {
1062                            IOUtil.closeEL(is);
1063                    }
1064            }
1065            
1066            public static void moveTo(Resource src, Resource dest) throws IOException {
1067                    ResourceUtil.checkMoveToOK(src, dest);
1068                    
1069                    if(src.isFile()){
1070                            if(!dest.exists()) dest.createFile(false);
1071                            IOUtil.copy(src,dest);
1072                            src.remove(false);
1073                    }
1074                    else {
1075                            if(!dest.exists()) dest.createDirectory(false);
1076                            Resource[] children = src.listResources();
1077                            for(int i=0;i<children.length;i++){
1078                                    moveTo(children[i],dest.getRealResource(children[i].getName()));
1079                            }
1080                            src.remove(false);
1081                    }
1082                    dest.setLastModified(System.currentTimeMillis());
1083            }
1084    
1085            /**
1086             * return the size of the Resource, other than method length of Resource this mthod return the size of all files in a directory
1087             * @param collectionDir
1088             * @return
1089             */
1090            public static long getRealSize(Resource res) {
1091                    return getRealSize(res,null);
1092            }
1093            
1094            /**
1095             * return the size of the Resource, other than method length of Resource this mthod return the size of all files in a directory
1096             * @param collectionDir
1097             * @return
1098             */
1099            public static long getRealSize(Resource res, ResourceFilter filter) {
1100                    if(res.isFile()) {
1101                            return res.length();
1102                    }
1103                    else if(res.isDirectory()) {
1104                            long size=0;
1105                            Resource[] children = filter==null?res.listResources():res.listResources(filter);
1106                            for(int i=0;i<children.length;i++) {
1107                                    size+=getRealSize(children[i]);
1108                            }
1109                            return size;
1110                    }
1111                    
1112                    return 0;
1113            }
1114    
1115    
1116            /**
1117             * return if Resource is empty, means is directory and has no children or a empty file,
1118             * if not exists return false.
1119             * @param res
1120             * @return
1121             */
1122            public static boolean isEmpty(Resource res) {
1123                    return isEmptyDirectory(res) || isEmptyFile(res);
1124            }
1125    
1126            public static boolean isEmptyDirectory(Resource res) {
1127                    if(res.isDirectory()) {
1128                            String[] children = res.list();
1129                            return children==null || children.length==0;
1130                    }
1131                    return false;
1132            }
1133            
1134            public static boolean isEmptyFile(Resource res) {
1135                    if(res.isFile()) {
1136                            return res.length()==0;
1137                    }
1138                    return false;
1139            }
1140    
1141            public static Resource toResource(File file) {
1142                    return ResourcesImpl.getFileResourceProvider().getResource(file.getPath());
1143            }
1144    
1145    
1146            /**
1147             * list childrn of all given resources
1148             * @param resources
1149             * @return
1150             */
1151            public static Resource[] listResources(Resource[] resources,ResourceFilter filter) {
1152                    int count=0;
1153                    Resource[] children;
1154                    ArrayList list=new ArrayList();
1155                    for(int i=0;i<resources.length;i++) {
1156                            children=filter==null?resources[i].listResources():resources[i].listResources(filter);
1157                            if(children!=null){
1158                                    count+=children.length;
1159                                    list.add(children);
1160                            }
1161                            else list.add(new Resource[0]);
1162                    }
1163                    Resource[] rtn=new Resource[count];
1164                    int index=0;
1165                    for(int i=0;i<resources.length;i++) {
1166                            children=(Resource[]) list.get(i);
1167                            for(int y=0;y<children.length;y++) {
1168                                    rtn[index++]=children[y];
1169                            }
1170                    }
1171                    //print.out(rtn);
1172                    return rtn;
1173            }
1174    
1175    
1176            public static Resource[] listResources(Resource res,ResourceFilter filter) {
1177                    return filter==null?res.listResources():res.listResources(filter);
1178            }
1179            
1180    
1181            public static void deleteFileOlderThan(Resource res, long date, ExtensionResourceFilter filter) {
1182                    if(res.isFile()) {
1183                            if(res.lastModified()<=date) res.delete();
1184                    }
1185                    else if(res.isDirectory()) {
1186                            Resource[] children = filter==null?res.listResources():res.listResources(filter);
1187                            for(int i=0;i<children.length;i++) {
1188                                    deleteFileOlderThan(children[i],date,filter);
1189                            }
1190                    }
1191            }
1192            
1193            /**
1194             * check if directory creation is ok with the rules for the Resource interface, to not change this rules.
1195             * @param resource
1196             * @param createParentWhenNotExists
1197             * @throws IOException
1198             */
1199            public static void checkCreateDirectoryOK(Resource resource, boolean createParentWhenNotExists) throws IOException {
1200                    if(resource.exists()) {
1201                            if(resource.isFile()) 
1202                                    throw new IOException("can't create directory ["+resource.getPath()+"], resource already exists as a file");
1203                            if(resource.isDirectory()) 
1204                                    throw new IOException("can't create directory ["+resource.getPath()+"], directory already exists");
1205                    }
1206                    
1207                    Resource parent = resource.getParentResource();
1208                    // when there is a parent but the parent does not exists
1209                    if(parent!=null) {
1210                            if(!parent.exists()) {
1211                                    if(createParentWhenNotExists)parent.createDirectory(true);
1212                                    else throw new IOException("can't create file ["+resource.getPath()+"], missng parent directory");
1213                            }
1214                            else if(parent.isFile()) {
1215                                    throw new IOException("can't create directory ["+resource.getPath()+"], parent is a file");
1216                            }
1217                    }
1218            }
1219    
1220    
1221            /**
1222             * check if file creating is ok with the rules for the Resource interface, to not change this rules.
1223             * @param resource
1224             * @param createParentWhenNotExists
1225             * @throws IOException
1226             */
1227            public static void checkCreateFileOK(Resource resource, boolean createParentWhenNotExists) throws IOException {
1228                    if(resource.exists()) {
1229                            if(resource.isDirectory()) 
1230                                    throw new IOException("can't create file ["+resource.getPath()+"], resource already exists as a directory");
1231                            if(resource.isFile()) 
1232                                    throw new IOException("can't create file ["+resource.getPath()+"], file already exists");
1233                    }
1234                    
1235                    Resource parent = resource.getParentResource();
1236                    // when there is a parent but the parent does not exists
1237                    if(parent!=null) {
1238                            if(!parent.exists()) {
1239                                    if(createParentWhenNotExists)parent.createDirectory(true);
1240                                    else throw new IOException("can't create file ["+resource.getPath()+"], missng parent directory");
1241                            }
1242                            else if(parent.isFile()) {
1243                                    throw new IOException("can't create file ["+resource.getPath()+"], parent is a file");
1244                            }
1245                    }
1246            }
1247    
1248            /**
1249             * check if copying a file is ok with the rules for the Resource interface, to not change this rules.
1250             * @param source
1251             * @param target
1252             * @throws IOException
1253             */
1254            public static void checkCopyToOK(Resource source, Resource target) throws IOException {
1255                    if(!source.isFile()) {
1256                            if(source.isDirectory())
1257                                    throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], source is a directory");
1258                            throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], source file does not exists");
1259                    }
1260                    else if(target.isDirectory()) {
1261                            throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], target is a directory");
1262                    }
1263            }
1264    
1265            /**
1266             * check if moveing a file is ok with the rules for the Resource interface, to not change this rules.
1267             * @param source
1268             * @param target
1269             * @throws IOException
1270             */
1271            public static void checkMoveToOK(Resource source, Resource target) throws IOException {
1272                    if(!source.exists()) {
1273                            throw new IOException("can't move ["+source.getPath()+"] to ["+target.getPath()+"], source file does not exists");
1274                    }
1275                    if(source.isDirectory() && target.isFile())
1276                            throw new IOException("can't move ["+source.getPath()+"] directory to ["+target.getPath()+"], target is a file");
1277                    if(source.isFile() && target.isDirectory())
1278                            throw new IOException("can't move ["+source.getPath()+"] file to ["+target.getPath()+"], target is a directory");
1279            }
1280    
1281            /**
1282             * check if getting a inputstream of the file is ok with the rules for the Resource interface, to not change this rules.
1283             * @param resource
1284             * @throws IOException
1285             */
1286            public static void checkGetInputStreamOK(Resource resource) throws IOException {
1287                    if(!resource.exists())
1288                            throw new IOException("file ["+resource.getPath()+"] does not exists");
1289                    
1290                    if(resource.isDirectory())
1291                            throw new IOException("can't read directory ["+resource.getPath()+"] as a file");
1292    
1293            }
1294    
1295            /**
1296             * check if getting a outputstream of the file is ok with the rules for the Resource interface, to not change this rules.
1297             * @param resource
1298             * @throws IOException
1299             */
1300            public static void checkGetOutputStreamOK(Resource resource) throws IOException {
1301                    if(resource.exists() && !resource.isWriteable()) {
1302                            throw new IOException("can't write to file ["+resource.getPath()+"],file is readonly");
1303                    }
1304                    if(resource.isDirectory())
1305                            throw new IOException("can't write directory ["+resource.getPath()+"] as a file");
1306                    if(!resource.getParentResource().exists())
1307                            throw new IOException("can't write file ["+resource.getPath()+"] as a file, missing parent directory ["+resource.getParent()+"]");
1308            }
1309    
1310            /**
1311             * check if removing the file is ok with the rules for the Resource interface, to not change this rules.
1312             * @param resource
1313             * @throws IOException
1314             */
1315            public static void checkRemoveOK(Resource resource) throws IOException {
1316                    if(!resource.exists())throw new IOException("can't delete resource "+resource+", resource does not exists");
1317                    if(!resource.canWrite())throw new IOException("can't delete resource "+resource+", no access");
1318                    
1319            }
1320            
1321            public static void deleteEmptyFolders(Resource res) throws IOException {
1322                    if(res.isDirectory()){
1323                            Resource[] children = res.listResources();
1324                            for(int i=0;i<children.length;i++){
1325                                    deleteEmptyFolders(children[i]);
1326                            }
1327                            if(res.listResources().length==0){
1328                                    res.remove(false);
1329                            }
1330                    }
1331            }
1332            
1333            // FUTURE this method should be part of pagesource in a more proper way, there should be a method getResource() inside PageSource
1334            public static Resource getResource(PageContext pc,PageSource ps) throws ExpressionException {
1335                    Resource res = ps.getPhyscalFile();
1336                    
1337                    // there is no physical resource
1338                    if(res==null){
1339                    String path=ps.getDisplayPath();
1340                    if(path.startsWith("ra://"))
1341                            path="zip://"+path.substring(5);
1342                    res=ResourceUtil.toResourceExisting(pc, path,false);
1343            }
1344                    return res;
1345            }
1346            
1347            public static Resource getResource(PageContext pc,PageSource ps, Resource defaultValue) {
1348                    try {
1349                            return getResource(pc, ps);
1350                    } 
1351                    catch (Throwable t) {
1352                            return defaultValue;
1353                    }
1354            }
1355            
1356            public static int directrySize(Resource dir,ResourceFilter filter) {
1357                    if(dir==null || !dir.isDirectory()) return 0;
1358                    if(filter==null) return dir.list().length;
1359                    return dir.list(filter).length;
1360            }
1361            
1362            public static int directrySize(Resource dir,ResourceNameFilter filter) {
1363                    if(dir==null || !dir.isDirectory()) return 0;
1364                    if(filter==null) return dir.list().length;
1365                    return dir.list(filter).length;
1366            }
1367            
1368            public static String[] names(Resource[] resources) {
1369                    String[] names=new String[resources.length];
1370                    for(int i=0;i<names.length;i++){
1371                            names[i]=resources[i].getName();
1372                    }
1373                    return names;
1374            }
1375    
1376    }