001    package railo.runtime.tag;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.ByteArrayOutputStream;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.util.ArrayList;
008    import java.util.Enumeration;
009    import java.util.HashSet;
010    import java.util.List;
011    import java.util.Set;
012    import java.util.zip.ZipEntry;
013    import java.util.zip.ZipException;
014    import java.util.zip.ZipFile;
015    import java.util.zip.ZipInputStream;
016    import java.util.zip.ZipOutputStream;
017    
018    import org.apache.oro.text.regex.MalformedPatternException;
019    
020    import railo.commons.io.IOUtil;
021    import railo.commons.io.compress.ZipUtil;
022    import railo.commons.io.res.Resource;
023    import railo.commons.io.res.filter.DirectoryResourceFilter;
024    import railo.commons.io.res.filter.FileResourceFilter;
025    import railo.commons.io.res.filter.OrResourceFilter;
026    import railo.commons.io.res.filter.ResourceFilter;
027    import railo.commons.io.res.util.FileWrapper;
028    import railo.commons.io.res.util.ResourceUtil;
029    import railo.commons.io.res.util.WildCardFilter;
030    import railo.commons.lang.StringUtil;
031    import railo.runtime.exp.ApplicationException;
032    import railo.runtime.exp.ExpressionException;
033    import railo.runtime.exp.PageException;
034    import railo.runtime.ext.tag.BodyTagImpl;
035    import railo.runtime.op.Caster;
036    import railo.runtime.op.Decision;
037    import railo.runtime.type.QueryImpl;
038    import railo.runtime.type.dt.DateTimeImpl;
039    
040    public final class Zip extends BodyTagImpl {
041    
042            private String action="zip";
043            private String charset;
044            private Resource destination;
045            private String entryPath;
046            private Resource file;
047            private WildCardFilter filter;
048            private String name;
049            private boolean overwrite;
050            private String prefix;
051            private boolean recurse=true;
052            private boolean showDirectory;
053            private boolean storePath=true;
054            private String variable;
055            private List<Object> params;
056            private Set<String> alreadyUsed;
057            private Resource source;
058            private static int id=0;
059            
060            
061        /**
062        * @see javax.servlet.jsp.tagext.Tag#release()
063        */
064        public void release()   {
065            super.release();
066            action="zip";
067            charset=null;
068            destination=null;
069            entryPath=null;
070            file=null;
071            filter=null;
072            name=null;
073            overwrite=false;
074            prefix=null;
075            recurse=true;
076            showDirectory=false;
077            source=null;
078            storePath=true;
079            variable=null;
080    
081            if(params!=null)params.clear();
082            if(alreadyUsed!=null)alreadyUsed.clear();
083        }
084            
085        
086        
087        /**
088             * @param action the action to set
089             */
090            public void setAction(String action) {
091                    this.action = action.trim().toLowerCase();
092            }
093    
094    
095    
096            /**
097             * @param charset the charset to set
098             */
099            public void setCharset(String charset) {
100                    this.charset = charset;
101            }
102    
103    
104    
105            /**
106             * @param destination the destination to set
107             * @throws ExpressionException 
108             * @throws PageException 
109             */
110            public void setDestination(String strDestination) throws PageException {
111                    this.destination = ResourceUtil.toResourceExistingParent(pageContext, strDestination);
112                    if(!destination.exists())destination.mkdirs();
113                    
114                    if(!destination.isDirectory())
115                            throw new ApplicationException("destination ["+strDestination+"] is not a existing directory");
116                    
117                    
118            }
119    
120    
121    
122            /**
123             * @param entryPath the entryPath to set
124             */
125            public void setEntrypath(String entryPath) {
126                    if(StringUtil.isEmpty(entryPath,true)) return;
127    
128                    entryPath=entryPath.trim();
129                    entryPath=entryPath.replace('\\','/');
130                    
131                    if(StringUtil.startsWith(entryPath,'/'))entryPath=entryPath.substring(1);
132                    if(StringUtil.endsWith(entryPath,'/'))entryPath=entryPath.substring(0,entryPath.length()-1);
133                    this.entryPath = entryPath;
134            }
135    
136    
137    
138            /**
139             * @param file the file to set
140             * @throws ExpressionException 
141             */
142            public void setFile(String file) {
143                    this.file = ResourceUtil.toResourceNotExisting(pageContext, file);
144            }
145    
146            /**
147             * @param filter the filter to set
148             */
149            public void setFilter(String filter) throws PageException {
150                    try {
151                            this.filter = new WildCardFilter(filter);
152                    } 
153                    catch (MalformedPatternException e) {
154                            throw Caster.toPageException(e);
155                    }
156            }
157    
158    
159    
160            /**
161             * @param name the name to set
162             */
163            public void setName(String name) {
164                    this.name = name;
165            }
166    
167    
168    
169            /**
170             * @param overwrite the overwrite to set
171             */
172            public void setOverwrite(boolean overwrite) {
173                    this.overwrite = overwrite;
174            }
175    
176    
177    
178            /**
179             * @param prefix the prefix to set
180             */
181            public void setPrefix(String prefix) {
182                    this.prefix = prefix;
183            }
184    
185    
186    
187            /**
188             * @param recurse the recurse to set
189             */
190            public void setRecurse(boolean recurse) {
191                    this.recurse = recurse;
192            }
193    
194    
195    
196            /**
197             * @param showDirectory the showDirectory to set
198             */
199            public void setShowdirectory(boolean showDirectory) {
200                    this.showDirectory = showDirectory;
201            }
202    
203    
204    
205            /**
206             * @param source the source to set
207             * @throws PageException 
208             */
209            public void setSource(String strSource) throws PageException {
210                    source = ResourceUtil.toResourceExisting(pageContext, strSource);
211                    
212            }
213    
214    
215    
216            /**
217             * @param storePath the storePath to set
218             */
219            public void setStorepath(boolean storePath) {
220                    this.storePath = storePath;
221            }
222    
223    
224    
225            /**
226             * @param variable the variable to set
227             */
228            public void setVariable(String variable) {
229                    this.variable = variable;
230            }
231    
232    
233    
234            /**
235            * @throws PageException 
236         * @see javax.servlet.jsp.tagext.Tag#doStartTag()
237            */
238            public int doStartTag() throws PageException    {
239                    return EVAL_BODY_INCLUDE;
240            }
241    
242            private void actionDelete() throws ApplicationException, IOException {
243                    required("file",file,true);
244                    
245    
246                    Resource existing = pageContext.getConfig().getTempDirectory().getRealResource(getTempName());
247                    IOUtil.copy(file, existing);
248                    
249                    ZipInputStream zis = null;  
250                    ZipOutputStream zos = null;
251                    try {
252                            zis = new ZipInputStream( IOUtil.toBufferedInputStream(existing.getInputStream()) );  
253                            zos = new ZipOutputStream(IOUtil.toBufferedOutputStream(file.getOutputStream()));
254                            
255                    ZipEntry entry;
256                    String path,name;
257                    int index;
258                    boolean accept;
259                    
260                    if(filter==null && recurse && entryPath==null)
261                            throw new ApplicationException("define at least one restriction, can't delete all the entries from a zip file");
262                    
263                    while ( ( entry = zis.getNextEntry()) != null ) {
264                            accept=false;
265                            path = entry.getName().replace('\\', '/');
266                    index=path.lastIndexOf('/');
267                    
268                            if(!recurse && index>0) accept=true;
269                            
270                            //dir=index==-1?"":path.substring(0,index);
271                    name=path.substring(index+1);
272                    
273                    if(filter!=null && !filter.accept(name)) accept=true;
274                    if(!entryPathMatch(path)) accept=true;
275                            
276                    if(!accept) continue;
277                            
278                            add(zos, entry, zis, false);
279                            zis.closeEntry();
280                    }
281                    }
282                    finally {
283                            IOUtil.closeEL(zis);
284                            IOUtil.closeEL(zos);
285                            existing.delete();
286                    }
287                    
288            }
289    
290    
291    
292            private void actionList() throws PageException, IOException {
293                    required("file",file,true);
294                    required("name",name);
295                    
296                    
297                    
298                    
299                    railo.runtime.type.Query query=new QueryImpl(
300                    new String[]{"name","size","type","dateLastModified","directory","crc","compressedSize","comment"},
301                    0,"query");
302                    pageContext.setVariable(name, query);
303            
304                    ZipFile zip = getZip(file);
305                    Enumeration entries = zip.entries();
306                    
307            try {
308                    String path,name,dir;
309                ZipEntry ze;
310                int row=0,index;
311                while(entries.hasMoreElements()) {
312                    ze = (ZipEntry)entries.nextElement();
313                    if(!showDirectory && ze.isDirectory()) continue;
314                    
315                    path = ze.getName().replace('\\', '/');
316                    index=path.lastIndexOf('/');
317                    if(!recurse && index>0) continue;
318                    
319                    dir=index==-1?"":path.substring(0,index);
320                    name=path.substring(index+1);
321                                    
322                    if(filter!=null && !filter.accept(null, name)) continue;
323                    
324                    
325                    if(!entryPathMatch(dir)) continue;
326                    //if(entryPath!=null && !(dir.equalsIgnoreCase(entryPath) || StringUtil.startsWithIgnoreCase(dir,entryPath+"/"))) ;///continue;
327                    
328                    row++;
329                    query.addRow();
330                    query.setAt("name", row, path);
331                    query.setAt("size", row, Caster.toDouble(ze.getSize()));
332                    query.setAt("type", row, ze.isDirectory()?"Directory":"File");
333                    query.setAt("dateLastModified", row, new DateTimeImpl(pageContext,ze.getTime(),false));
334                    query.setAt("crc", row, Caster.toDouble(ze.getCrc()));
335                    query.setAt("compressedSize", row, Caster.toDouble(ze.getCompressedSize()));
336                    query.setAt("comment", row, ze.getComment());
337                    query.setAt("directory", row, dir);
338                    //zis.closeEntry();
339                    
340                }
341            }
342            finally {
343                    IOUtil.closeEL(zip);
344            }   
345            }
346    
347            private boolean entryPathMatch(String dir) {
348                    if(entryPath==null) return true;
349                    
350                    return dir.equalsIgnoreCase(entryPath) || StringUtil.startsWithIgnoreCase(dir,entryPath+"/");
351            }
352    
353    
354    
355            private void actionRead(boolean binary) throws ZipException, IOException, PageException {
356                    required("file",file,true);
357                    required("variable",variable);
358                    required("entrypath",variable);
359                    ZipFile zip = getZip(file);
360                    
361                    try {
362                            ZipEntry ze = zip.getEntry(entryPath);
363                            if(ze==null)ze = zip.getEntry(entryPath+"/");
364                            if(ze==null) throw new ApplicationException("zip file ["+file+"] has no entry with name ["+entryPath+"]");
365                            
366                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
367                            
368                            InputStream is = zip.getInputStream(ze);
369                            IOUtil.copy(is, baos,true,false);
370                            zip.close();
371                            
372                            if(binary)
373                                    pageContext.setVariable(variable, baos.toByteArray());
374                            else {
375                                    if(charset==null)charset=pageContext.getConfig().getResourceCharset();
376                                    pageContext.setVariable(variable, new String(baos.toByteArray(),charset));
377                            }
378                    }
379                    finally {
380                            IOUtil.closeEL(zip);
381                    }
382                    
383            }
384    
385    
386    
387            private void actionUnzip() throws ApplicationException, IOException {
388                    required("file",file,true);
389                    required("destination",destination,false);
390    
391                    ZipInputStream zis=null;
392                    String path;
393                    Resource target,parent;
394                    int index;
395            try {
396                    
397                    zis = new ZipInputStream( IOUtil.toBufferedInputStream(file.getInputStream()) ) ;     
398                    ZipEntry entry;
399                    while ( ( entry = zis.getNextEntry()) != null ) {
400                            
401                            path = entry.getName().replace('\\', '/');
402                    index=path.lastIndexOf('/');
403                    
404                    // recurse
405                    if(!recurse && index!=-1) {
406                            zis.closeEntry();
407                            continue;
408                    }
409                    // filter
410                    if(filter!=null && !filter.accept(path.substring(index+1))) {
411                            zis.closeEntry();
412                            continue;
413                    }
414                    // entrypath
415                    if(!entryPathMatch(path)) {
416                            zis.closeEntry();
417                            continue;
418                    }
419                            target=destination.getRealResource(entry.getName());
420                            if(!storePath) target=destination.getRealResource(target.getName());
421                        if(entry.isDirectory()) {
422                            target.mkdirs();
423                        }
424                        else {
425                            if(storePath){
426                                    parent=target.getParentResource();
427                                    if(!parent.exists())parent.mkdirs();
428                            }
429                            if(overwrite || !target.exists())IOUtil.copy(zis,target,false);
430                        }
431                        target.setLastModified(entry.getTime());
432                        zis.closeEntry() ;
433                    }
434            }
435            finally {
436                    IOUtil.closeEL(zis);
437            }
438        }
439    
440    
441    
442            private void actionZip() throws PageException, IOException {
443                    required("file",file,false);
444                    Resource dir = file.getParentResource();
445                    
446                    if(!dir.exists()) {
447                            throw new ApplicationException("directory ["+dir.toString()+"] doesn't exist"); 
448                    }
449                    
450                    
451                    
452                    if((params==null || params.isEmpty()) && source!=null) {
453                            setParam(new ZipParamSource(source,entryPath,filter,prefix,recurse));
454                    }
455    
456                    if((params==null || params.isEmpty())) {
457                            throw new ApplicationException("No source/content specified");
458                    }
459                    
460                    
461                    
462                    
463                    ZipOutputStream zos=null;
464                    Resource existing=null;
465                    try {
466                            
467                            // existing
468                            if(!overwrite && file.exists()) {
469                                    existing = pageContext.getConfig().getTempDirectory().getRealResource(getTempName());
470                                    IOUtil.copy(file, existing);
471                            }
472                            
473                                    zos = new ZipOutputStream(IOUtil.toBufferedOutputStream(file.getOutputStream()));
474    
475                            Object[] arr = params.toArray();
476                            for(int i=arr.length-1;i>=0;i--) {
477                                    if(arr[i] instanceof ZipParamSource)
478                                            actionZip(zos,(ZipParamSource)arr[i]);
479                                    else if(arr[i] instanceof ZipParamContent)
480                                            actionZip(zos,(ZipParamContent)arr[i]);
481                            }
482    
483                            if(existing!=null) {
484                                    ZipInputStream zis = new ZipInputStream( IOUtil.toBufferedInputStream(existing.getInputStream()) );  
485                                    try {
486                                    ZipEntry entry;
487                                    while ( ( entry = zis.getNextEntry()) != null ) {
488                                            add(zos, entry, zis, false);
489                                            zis.closeEntry();
490                                    }
491                                    }
492                                    finally {
493                                            zis.close();
494                                    }
495                            }
496                    }
497                    finally {
498                            ZipUtil.cloeseEL(zos);
499                            if(existing!=null)existing.delete();
500                            
501                    }
502                    
503            }
504    
505    
506    
507            private String getTempName() {
508                    return "tmp-"+(id++)+".zip";
509            }
510    
511    
512    
513            private void actionZip(ZipOutputStream zos, ZipParamContent zpc) throws PageException, IOException {
514                    Object content = zpc.getContent();
515                    if(Decision.isBinary(content)) {
516                            add(zos, new ByteArrayInputStream(Caster.toBinary(content)), zpc.getEntryPath(), System.currentTimeMillis(), true);
517                            
518                    }
519                    else {
520                            String charset=zpc.getCharset();
521                            if(StringUtil.isEmpty(charset))charset=pageContext.getConfig().getResourceCharset();
522                            add(zos, new ByteArrayInputStream(content.toString().getBytes(charset)), zpc.getEntryPath(), System.currentTimeMillis(), true);
523                    }
524            }
525    
526    
527    
528            private void actionZip(ZipOutputStream zos, ZipParamSource zps) throws IOException {
529                    if(zps.getSource().isFile()){
530                            
531                            String ep = zps.getEntryPath();
532                            if(ep==null)ep=zps.getSource().getName();
533                            add(zos,zps.getSource().getInputStream(),ep,zps.getSource().lastModified(),true);
534                    }       
535                    else {
536                            
537                            // prefix
538                            String p=zps.getPrefix();
539                            if(StringUtil.isEmpty(p))
540                                    p=this.prefix;
541                            
542                            if(!StringUtil.isEmpty(p)){
543                                    if(!StringUtil.endsWith(p, '/'))p+="/";
544                            }
545                            else 
546                                    p="";
547                            
548                            // recurse
549                            
550                            
551                            // filter
552                            ResourceFilter f = zps.getFilter();
553                            if(f==null)f=this.filter;
554                            if(zps.isRecurse()) {
555                                    if(f!=null)f=new OrResourceFilter(new ResourceFilter[]{DirectoryResourceFilter.FILTER,f});
556                            }
557                            else {
558                                    if(f==null)f=FileResourceFilter.FILTER;
559                            }
560                            
561                            addDir(zos,zps.getSource(),p,f);
562                    }
563            }
564    
565    
566    
567            private void addDir(ZipOutputStream zos, Resource dir, String parent, ResourceFilter filter) throws IOException {
568                    Resource[] children = filter==null?dir.listResources():dir.listResources(filter);
569                    
570                    for(int i=0;i<children.length;i++) {
571                            
572                            
573                            if(children[i].isDirectory()) addDir(zos, children[i], parent+children[i].getName()+"/",filter);
574                            else {
575                                    add(zos, children[i].getInputStream(), parent+children[i].getName(), children[i].lastModified(), true);
576                            }
577                    }
578            }
579    
580    
581    
582            private void add(ZipOutputStream zos, InputStream is, String path, long lastMod, boolean closeInput) throws IOException {
583                    ZipEntry ze=new ZipEntry(path);
584                    ze.setTime(lastMod);
585                    add(zos, ze, is, closeInput);
586            }
587            
588            private void add(ZipOutputStream zos, ZipEntry entry,InputStream is, boolean closeInput) throws IOException {
589                    if(alreadyUsed==null)alreadyUsed=new HashSet<String>();
590                    else if(alreadyUsed.contains(entry.getName())) return;
591                    zos.putNextEntry(entry);
592            try {
593                IOUtil.copy(is,zos,closeInput,false);
594            } 
595            finally {
596                zos.closeEntry();
597            }
598                    alreadyUsed.add(entry.getName());
599            }
600    
601    
602    
603            /**
604            * @see javax.servlet.jsp.tagext.BodyTag#doInitBody()
605            */
606            public void doInitBody()        {
607                    
608            }
609    
610            /**
611            * @see javax.servlet.jsp.tagext.BodyTag#doAfterBody()
612            */
613            public int doAfterBody()        {
614                    return SKIP_BODY;
615            }
616        
617        /**
618         * @throws PageException 
619         * @see javax.servlet.jsp.tagext.Tag#doEndTag()
620            */
621            public int doEndTag() throws PageException      {//print.out("doEndTag"+doCaching+"-"+body);
622                    try {
623                            if(action.equals("delete")) actionDelete();
624                            else if(action.equals("list")) actionList();
625                            else if(action.equals("read")) actionRead(false);
626                            else if(action.equals("readbinary")) actionRead(true);
627                            else if(action.equals("unzip")) actionUnzip();
628                            else if(action.equals("zip")) actionZip();
629                    else 
630                                    throw new ApplicationException("invalid value ["+action+"] for attribute action","values for attribute action are:info,move,rename,copy,delete,read,readbinary,write,append,upload");
631                    }
632                    catch(IOException ioe) {
633                            throw Caster.toPageException(ioe);
634                    }
635                    
636                    return EVAL_PAGE;
637            }
638    
639            /**
640         * sets if tag has a body or not
641         * @param hasBody
642         */
643        public void hasBody(boolean hasBody) {
644           ///this.hasBody=hasBody;
645        }
646    
647        private ZipFile getZip(Resource file) throws ZipException, IOException {
648                    return new ZipFile(FileWrapper.toFile(file));
649            }
650        
651        /**
652             * throw a error if the value is empty (null)
653         * @param attributeName
654         * @param atttributValue
655             * @throws ApplicationException
656         */
657        private void required(String attributeName, String attributValue) throws ApplicationException {
658            if(StringUtil.isEmpty(attributValue))
659                throw new ApplicationException(
660                        "invalid attribute constellation for the tag zip", 
661                        "attribute ["+attributeName+"] is required, if action is ["+action+"]");
662        }
663    
664        /**
665             * throw a error if the value is empty (null)
666         * @param attributeName
667         * @param atttributValue
668             * @throws ApplicationException
669         */
670        private void required(String attributeName, Resource attributValue, boolean exists) throws ApplicationException {
671            if(attributValue==null)
672                throw new ApplicationException(
673                        "invalid attribute constellation for the tag zip", 
674                        "attribute ["+attributeName+"] is required, if action is ["+action+"]");
675            
676            if(exists && !attributValue.exists())
677                    throw new ApplicationException(attributeName+" resource ["+attributValue+"] doesn't exist");
678            else if(exists && !attributValue.canRead())
679                            throw new ApplicationException("no access to "+attributeName+" resource ["+attributValue+"]");
680                    
681            
682        }
683    
684    
685    
686    
687            public void setParam(Object param) {
688                    if(params==null) {
689                            params=new ArrayList<Object>();
690                            alreadyUsed=new HashSet<String>();
691                    }
692                    params.add(param);
693            }
694    
695    
696    
697            /**
698             * @return the source
699             */
700            public Resource getSource() {
701                    return source;
702            }
703        
704        
705    }