001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.io;
020
021import java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.Enumeration;
030import java.util.zip.GZIPInputStream;
031import java.util.zip.GZIPOutputStream;
032import java.util.zip.ZipEntry;
033import java.util.zip.ZipFile;
034import java.util.zip.ZipInputStream;
035import java.util.zip.ZipOutputStream;
036
037import lucee.aprint;
038import lucee.commons.io.res.Resource;
039import lucee.commons.io.res.ResourceProvider;
040import lucee.commons.io.res.ResourcesImpl;
041import lucee.commons.io.res.filter.ExtensionResourceFilter;
042import lucee.commons.io.res.filter.OrResourceFilter;
043import lucee.commons.io.res.filter.ResourceFilter;
044import lucee.commons.lang.StringUtil;
045import lucee.runtime.op.Caster;
046
047import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
048import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
049import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
050import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
051
052/**
053 * Util to manipulate zip files
054 */
055public final class CompressUtil {
056
057    /**
058     * Field <code>FORMAT_ZIP</code>
059     */
060    public static final int FORMAT_ZIP=0;
061    /**
062     * Field <code>FORMAT_TAR</code>
063     */
064    public static final int FORMAT_TAR=1;
065    /**
066     * Field <code>FORMAT_TGZ</code>
067     */
068    public static final int FORMAT_TGZ=2;
069    /**
070     * Field <code>FORMAT_GZIP</code>
071     */
072    public static final int FORMAT_GZIP=3;
073    /**
074     * Field <code>FORMAT_BZIP</code>
075     */
076    public static final int FORMAT_BZIP=4;
077    /**
078     * Field <code>FORMAT_BZIP</code>
079     */
080    public static final int FORMAT_BZIP2=4;
081
082    /**
083     * Field <code>FORMAT_TBZ</code>
084     */
085    public static final int FORMAT_TBZ=5;
086    /**
087     * Field <code>FORMAT_TBZ2</code>
088     */
089    public static final int FORMAT_TBZ2=5;
090    
091    /**
092     * Constructor of the class
093     */
094    private CompressUtil(){}
095
096    /**
097     * extract a zip file to a directory
098     * @param format 
099     * @param source 
100     * @param target 
101     * @throws IOException
102    */
103    public static void extract(int format,Resource source, Resource target) throws IOException {
104        if(format==FORMAT_ZIP)          extractZip(source,target);
105        else if(format==FORMAT_TAR)     extractTar(source,target);
106        else if(format==FORMAT_GZIP)extractGZip(source,target);
107        else if(format==FORMAT_TGZ)     extractTGZ(source,target);
108        else throw new IOException("can't extract in given format");
109    }
110    
111    public static void list(int format,Resource source) throws IOException {
112        if(format==FORMAT_ZIP)          listZip(source);
113        //else if(format==FORMAT_TAR)   listar(source);
114        //else if(format==FORMAT_GZIP)listGZip(source);
115        //else if(format==FORMAT_TGZ)   listTGZ(source);
116        else throw new IOException("can't list in given format, atm only zip files are supported");
117    }
118    
119    
120
121    private static void extractTGZ(Resource source, Resource target) throws IOException {
122        //File tmpTarget = File.createTempFile("_temp","tmp");
123        Resource tmp = SystemUtil.getTempDirectory().getRealResource(System.currentTimeMillis()+".tmp");
124        try {
125                // read Gzip
126                extractGZip(source, tmp);
127                
128                // read Tar
129                extractTar(tmp, target);
130        }
131        finally {
132                tmp.delete();
133        }
134        }
135
136        private static void extractGZip(Resource source, Resource target) throws IOException {
137        InputStream is=null;
138        OutputStream os=null;
139                try {
140                        is = new GZIPInputStream(IOUtil.toBufferedInputStream(source.getInputStream()));
141                        os = IOUtil.toBufferedOutputStream(target.getOutputStream());
142                        IOUtil.copy(is,os,false,false);
143                } 
144                finally {
145                        IOUtil.closeEL(is, os);
146                }
147        }
148
149        /**
150     * extract a zip file to a directory
151     * @param format 
152     * @param sources
153     * @param target 
154     * @throws IOException
155    */
156    public static void extract(int format,Resource[] sources, Resource target) throws IOException {
157        if(format==FORMAT_ZIP  || format==FORMAT_TAR) {
158            for(int i=0;i<sources.length;i++) {
159                extract(format,sources[i],target);
160            }
161        }
162        else throw new IOException("can't extract in given format");
163    }
164    
165    private static void extractTar(Resource tarFile, Resource targetDir) throws IOException {
166        if(!targetDir.exists() || !targetDir.isDirectory())
167            throw new IOException(targetDir+" is not a existing directory");
168        
169        if(!tarFile.exists())
170            throw new IOException(tarFile+" is not a existing file");
171        
172        if(tarFile.isDirectory()) {
173                Resource[] files = tarFile.listResources(new ExtensionResourceFilter("tar"));
174            if(files==null) 
175                throw new IOException("directory "+tarFile+" is empty");
176            extract(FORMAT_TAR,files,targetDir);
177            return;
178        }
179        
180//      read the zip file and build a query from its contents
181        TarArchiveInputStream tis=null;
182        try {
183                tis = new TarArchiveInputStream( IOUtil.toBufferedInputStream(tarFile.getInputStream()) ) ;     
184                TarArchiveEntry entry;
185                int mode;
186                while ( ( entry = tis.getNextTarEntry()) != null ) {
187                        //print.ln(entry);
188                        Resource target=targetDir.getRealResource(entry.getName());
189                    if(entry.isDirectory()) {
190                        target.mkdirs();
191                    }
192                    else {
193                        Resource parent=target.getParentResource();
194                        if(!parent.exists())parent.mkdirs();
195                        IOUtil.copy(tis,target,false);
196                    }
197                    target.setLastModified(entry.getModTime().getTime());
198                    mode=entry.getMode();
199                    if(mode>0)target.setMode(mode);
200                    //tis.closeEntry() ;
201                }
202        }
203        finally {
204                IOUtil.closeEL(tis);
205        }
206    }
207    
208    private static void extractZip(Resource zipFile, Resource targetDir) throws IOException {
209        if(!targetDir.exists() || !targetDir.isDirectory())
210            throw new IOException(targetDir+" is not a existing directory");
211        
212        if(!zipFile.exists())
213            throw new IOException(zipFile+" is not a existing file");
214        
215        if(zipFile.isDirectory()) {
216                Resource[] files = zipFile.listResources(
217                    new OrResourceFilter(new ResourceFilter[]{
218                            new ExtensionResourceFilter("zip"),
219                            new ExtensionResourceFilter("jar"),
220                            new ExtensionResourceFilter("war"),
221                            new ExtensionResourceFilter("tar"),
222                            new ExtensionResourceFilter("ear")
223                    })
224            );
225            if(files==null) 
226                throw new IOException("directory "+zipFile+" is empty");
227            extract(FORMAT_ZIP,files,targetDir);
228            return;
229        }
230        
231//      read the zip file and build a query from its contents
232        unzip(zipFile,targetDir);
233        /*ZipInputStream zis=null;
234        try {
235                zis = new ZipInputStream( IOUtil.toBufferedInputStream(zipFile.getInputStream()) ) ;     
236                ZipEntry entry;
237                while ( ( entry = zis.getNextEntry()) != null ) {
238                        Resource target=targetDir.getRealResource(entry.getName());
239                    if(entry.isDirectory()) {
240                        target.mkdirs();
241                    }
242                    else {
243                        Resource parent=target.getParentResource();
244                        if(!parent.exists())parent.mkdirs();
245                        
246                        IOUtil.copy(zis,target,false);
247                    }
248                    target.setLastModified(entry.getTime());
249                   zis.closeEntry() ;
250                }
251        }
252        finally {
253                IOUtil.closeEL(zis);
254        }*/
255    }
256    
257
258    
259
260    private static void unzip(Resource zipFile,Resource targetDir) throws IOException {
261        /*if(zipFile instanceof File){
262                unzip((File)zipFile, targetDir);
263                return;
264        }*/
265    
266        ZipInputStream zis=null;
267        try {
268                zis = new ZipInputStream( IOUtil.toBufferedInputStream(zipFile.getInputStream()) ) ;     
269                ZipEntry entry;
270                while ( ( entry = zis.getNextEntry()) != null ) {
271                        Resource target=targetDir.getRealResource(entry.getName());
272                    if(entry.isDirectory()) {
273                        target.mkdirs();
274                    }
275                    else {
276                        Resource parent=target.getParentResource();
277                        if(!parent.exists())parent.mkdirs();
278                        IOUtil.copy(zis,target,false);
279                    }
280                    target.setLastModified(entry.getTime());
281                    zis.closeEntry() ;
282                }
283        }
284        finally {
285                IOUtil.closeEL(zis);
286        }
287        }
288
289    private static void listZip(Resource zipFile) throws IOException {
290        if(!zipFile.exists())
291            throw new IOException(zipFile+" is not a existing file");
292        
293        if(zipFile.isDirectory()) {
294                 throw new IOException(zipFile+" is a directory");
295        }
296        
297        ZipInputStream zis=null;
298        try {
299                zis = new ZipInputStream( IOUtil.toBufferedInputStream(zipFile.getInputStream()) ) ;     
300                ZipEntry entry;
301                while ( ( entry = zis.getNextEntry()) != null ) {
302                        if(!entry.isDirectory()){
303                                ByteArrayOutputStream baos=new ByteArrayOutputStream(); 
304                                IOUtil.copy(zis,baos,false,false);
305                                byte[] barr = baos.toByteArray();
306                                aprint.o(entry.getName()+":"+barr.length);
307                        }
308                }
309        }
310        finally {
311                IOUtil.closeEL(zis);
312        }
313        }
314    
315    private static void unzip2(File zipFile,Resource targetDir) throws IOException {
316        ZipFile zf=null;
317        try {
318                zf = new ZipFile(zipFile);
319                
320                ZipEntry entry;
321                Enumeration en = zf.entries();
322                while(en.hasMoreElements()){
323                        entry = (ZipEntry) en.nextElement();
324                        Resource target=targetDir.getRealResource(entry.getName());
325                    if(entry.isDirectory()) {
326                        target.mkdirs();
327                    }
328                    else {
329                        Resource parent=target.getParentResource();
330                        if(!parent.exists())parent.mkdirs();
331                        InputStream is = zf.getInputStream(entry);
332                        IOUtil.copy(is,target,true);
333                    }
334                    target.setLastModified(entry.getTime());
335                }
336        }
337        finally {
338                IOUtil.closeEL(zf);
339        }
340        }
341
342        /**
343     * compress data to a zip file
344     * @param format format it that should by compressed usally is CompressUtil.FORMAT_XYZ
345     * @param source
346     * @param target
347     * @param includeBaseFolder 
348     * @param mode 
349     * @throws IOException
350     */
351    public static void compress(int format, Resource source, Resource target, boolean includeBaseFolder, int mode) throws IOException {
352        if(     format==FORMAT_GZIP)    compressGZip(source,target);
353        else if(format==FORMAT_BZIP2)   compressBZip2(source,target);
354        else {
355                Resource[] sources=(!includeBaseFolder && source.isDirectory())?source.listResources():new Resource[]{source};
356            compress(format, sources, target,mode);
357        }
358    }
359
360    /**
361     * compress data to a zip file
362     * @param format format it that should by compressed usally is CompressUtil.FORMAT_XYZ
363     * @param sources
364     * @param target
365     * @param mode 
366     * @throws IOException
367     */
368    public static void compress(int format, Resource[] sources, Resource target, int mode) throws IOException {
369                
370        if(     format==FORMAT_ZIP)     compressZip(sources,target,null);
371        else if(format==FORMAT_TAR)     compressTar(sources,target,mode);
372        else if(format==FORMAT_TGZ)     compressTGZ(sources,target,mode);
373        else if(format==FORMAT_TBZ2)    compressTBZ2(sources,target,mode);
374
375        else throw new IOException("can't compress in given format");
376    }
377
378    /**
379     * compress a source file/directory to a tar/gzip file
380     * @param sources
381     * @param target
382     * @param mode 
383     * @throws IOException
384     */
385    public static void compressTGZ(Resource[] sources, Resource target,int mode) throws IOException {
386        File tmpTarget = File.createTempFile("_temp","tmp");
387        try {
388                // write Tar
389                OutputStream tmpOs=new FileOutputStream(tmpTarget);
390                try {
391                        compressTar(sources,tmpOs,mode);
392                }
393                finally {
394                        IOUtil.closeEL(tmpOs);
395                }
396                
397                // write Gzip
398                InputStream is = null;
399                OutputStream os = null;
400                try {
401                        is = new FileInputStream(tmpTarget);
402                        os = target.getOutputStream();
403                        compressGZip(is, os);
404                }
405                finally {
406                        IOUtil.closeEL(is,os);
407                }
408        }
409        finally {
410                tmpTarget.delete();
411        }
412    }
413    
414    /**
415     * compress a source file/directory to a tar/bzip2 file
416     * @param sources
417     * @param target
418     * @param mode 
419     * @throws IOException
420     */
421    private static void compressTBZ2(Resource[] sources, Resource target, int mode) throws IOException {
422        //File tmpTarget = File.createTempFile("_temp","tmp");
423        ByteArrayOutputStream baos=new ByteArrayOutputStream();
424        compressTar(sources,baos,mode);
425        _compressBZip2(new ByteArrayInputStream(baos.toByteArray()), target.getOutputStream());
426        //tmpTarget.delete();
427    }
428
429    /**
430     * compress a source file to a gzip file
431     * @param source
432     * @param target
433     * @throws IOException 
434     * @throws IOException
435     */
436    private static void compressGZip(Resource source, Resource target) throws IOException {
437        if(source.isDirectory()) {
438            throw new IOException("you can only create a GZIP File from a single source file, use TGZ (TAR-GZIP) to first TAR multiple files");
439        }
440        InputStream is=null;
441        OutputStream os=null;
442                try {
443                        is = source.getInputStream();
444                        os = target.getOutputStream();
445                } catch(IOException ioe) {
446                        IOUtil.closeEL(is, os);
447                        throw ioe;
448                }
449        compressGZip(is,os);
450        
451    }
452
453    public static void compressGZip(InputStream source, OutputStream target) throws IOException {
454        InputStream is = IOUtil.toBufferedInputStream(source);
455        if(!(target instanceof GZIPOutputStream)) target = new GZIPOutputStream(IOUtil.toBufferedOutputStream(target));
456        IOUtil.copy(is,target,true,true);       
457    }
458
459    /**
460     * compress a source file to a bzip2 file
461     * @param source
462     * @param target
463     * @throws IOException
464     */
465    private static void compressBZip2(Resource source, Resource target) throws IOException {
466        if(source.isDirectory()) {
467            throw new IOException("you can only create a BZIP File from a single source file, use TBZ (TAR-BZIP2) to first TAR multiple files");
468        }
469        InputStream is=null;
470        OutputStream os=null;
471        try {
472                is=source.getInputStream();
473                os=target.getOutputStream();
474        }
475        catch(IOException ioe) {
476                IOUtil.closeEL(is, os);
477                throw ioe;
478        }
479        
480        _compressBZip2(is, os);
481    }
482
483    /**
484     * compress a source file to a bzip2 file
485     * @param source
486     * @param target
487     * @throws IOException
488     */
489    private static void _compressBZip2(InputStream source, OutputStream target) throws IOException {
490        
491        InputStream is = IOUtil.toBufferedInputStream(source);
492        OutputStream os = new BZip2CompressorOutputStream(IOUtil.toBufferedOutputStream(target));
493        IOUtil.copy(is,os,true,true);
494    }
495    
496    /**
497     * compress a source file/directory to a zip file
498     * @param sources
499     * @param target
500     * @param filter 
501     * @throws IOException
502     */
503    public static void compressZip(Resource[] sources, Resource target, ResourceFilter filter) throws IOException {
504        ZipOutputStream zos = null;
505        try {
506                zos = new ZipOutputStream(IOUtil.toBufferedOutputStream(target.getOutputStream()));
507            compressZip("",sources, zos, filter);
508        }
509        finally {
510            IOUtil.closeEL(zos);
511        }
512    }
513    
514    public static void compressZip( Resource[] sources, ZipOutputStream zos, ResourceFilter filter) throws IOException {
515        compressZip("",sources, zos, filter);
516    }
517    
518
519    private static void compressZip(String parent, Resource[] sources, ZipOutputStream zos, ResourceFilter filter) throws IOException {
520        if(parent.length()>0)parent+="/";
521        for(int i=0;i<sources.length;i++) { 
522            compressZip(parent+sources[i].getName(),sources[i],zos,filter);
523        }
524    }
525    
526    private static void compressZip(String parent, Resource source, ZipOutputStream zos, ResourceFilter filter) throws IOException {
527        if(source.isFile()) {
528                //if(filter.accept(source)) {
529                        ZipEntry ze=new ZipEntry(parent);
530                        ze.setTime(source.lastModified());
531                        zos.putNextEntry(ze);
532                    try {
533                        IOUtil.copy(source,zos,false);
534                    } 
535                    finally {
536                        zos.closeEntry();
537                    }
538                //}
539        }
540        else if(source.isDirectory()) {
541                if(!StringUtil.isEmpty(parent)) {
542                        ZipEntry ze=new ZipEntry(parent+"/");
543                        ze.setTime(source.lastModified());
544                        try {
545                                zos.putNextEntry(ze);
546                        }
547                        catch(IOException ioe) {
548                                if(Caster.toString(ioe.getMessage()).indexOf("duplicate")==-1)throw ioe;
549                        }
550                        zos.closeEntry();
551                }
552            compressZip(parent, filter==null?source.listResources():source.listResources(filter),zos,filter);
553        }
554    }
555
556    /**
557     * compress a source file/directory to a tar file
558     * @param sources
559     * @param target
560     * @param mode 
561     * @throws IOException
562     */
563    public static void compressTar(Resource[] sources,Resource target, int mode) throws IOException {
564        compressTar(sources, IOUtil.toBufferedOutputStream(target.getOutputStream()), mode);
565    }
566    
567    public static void compressTar(Resource[] sources,OutputStream target, int mode) throws IOException {
568        if(target instanceof TarArchiveOutputStream){
569            compressTar("",sources,(TarArchiveOutputStream)target,mode);
570            return;
571        }
572        TarArchiveOutputStream tos=new TarArchiveOutputStream(target);
573        tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
574        try {
575            compressTar("",sources, tos,mode);
576        }
577        finally {
578                IOUtil.closeEL(tos);
579        }
580    }
581    
582    public static void compressTar(String parent, Resource[] sources, TarArchiveOutputStream tos, int mode) throws IOException {
583
584        if(parent.length()>0)parent+="/";
585        for(int i=0;i<sources.length;i++) { 
586            compressTar(parent+sources[i].getName(),sources[i],tos,mode);
587        }
588    }
589    
590    private static void compressTar(String parent, Resource source,TarArchiveOutputStream tos, int mode) throws IOException {
591        if(source.isFile()) {
592                //TarEntry entry = (source instanceof FileResource)?new TarEntry((FileResource)source):new TarEntry(parent);
593                TarArchiveEntry entry = new TarArchiveEntry(parent);
594            
595            entry.setName(parent);
596            
597            // mode
598            //100777 TODO ist das so ok?
599            if(mode>0)       entry.setMode(mode);
600            else if((mode=source.getMode())>0)       entry.setMode(mode);
601            
602            entry.setSize(source.length());
603            entry.setModTime(source.lastModified());
604            tos.putArchiveEntry(entry);
605            try {
606                IOUtil.copy(source,tos,false);
607            } 
608            finally {
609                        tos.closeArchiveEntry();
610            }
611        }
612        else if(source.isDirectory()) {
613            compressTar(parent, source.listResources(),tos,mode);
614        }
615    }
616
617    public static void main(String[] args) throws IOException {
618        ResourceProvider frp = ResourcesImpl.getFileResourceProvider();
619        Resource src = frp.getResource("/Users/mic/temp/a");
620        
621        Resource tgz = frp.getResource("/Users/mic/temp/b/a.tgz");
622        tgz.getParentResource().mkdirs();
623        Resource tar = frp.getResource("/Users/mic/temp/b/a.tar");
624        tar.getParentResource().mkdirs();
625        Resource zip = frp.getResource("/Users/mic/temp/b/a.zip");
626        zip.getParentResource().mkdirs();
627        
628        Resource tgz1 = frp.getResource("/Users/mic/temp/b/tgz");
629        tgz1.mkdirs();
630        Resource tar1 = frp.getResource("/Users/mic/temp/b/tar");
631        tar1.mkdirs();
632        Resource zip1 = frp.getResource("/Users/mic/temp/b/zip");
633        zip1.mkdirs();
634        
635        compressTGZ(new Resource[]{src}, tgz, -1);
636        compressTar(new Resource[]{src}, tar, -1);
637        compressZip(new Resource[]{src}, zip, null);
638        
639        extractTGZ(tgz, tgz1);
640        extractTar(tar, tar1);
641        extractZip(src, zip1);
642        
643        }
644}