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