001    package org.shiftone.jrat.inject.process;
002    
003    
004    import org.shiftone.jrat.core.JRatException;
005    import org.shiftone.jrat.inject.InjectorOptions;
006    import org.shiftone.jrat.inject.bytecode.Transformer;
007    import org.shiftone.jrat.util.Assert;
008    import org.shiftone.jrat.util.VersionUtil;
009    import org.shiftone.jrat.util.io.IOUtil;
010    import org.shiftone.jrat.util.io.OpenInputStream;
011    import org.shiftone.jrat.util.log.Logger;
012    import org.shiftone.jrat.util.regex.CompositeMatcher;
013    import org.shiftone.jrat.util.regex.Matcher;
014    
015    import java.io.File;
016    import java.io.IOException;
017    import java.io.InputStream;
018    import java.io.PrintStream;
019    import java.util.zip.Deflater;
020    import java.util.zip.ZipEntry;
021    import java.util.zip.ZipInputStream;
022    import java.util.zip.ZipOutputStream;
023    
024    
025    /**
026     * @author jeff@shiftone.org (Jeff Drost)
027     */
028    public class ArchiveFileProcessor extends AbstractFileProcessor {
029    
030        private static final Logger LOG = Logger.getLogger(ArchiveFileProcessor.class);
031        private static final Matcher EXTENTION_MATCHER =
032                CompositeMatcher.buildCompositeGlobMatcher("zip,jar,ear,war,sar,har");
033        private static final int BUFFER_SIZE = 1024 * 64;
034    
035        protected void processFile(Transformer transformer, InjectorOptions options, File source, File target) {
036    
037            LOG.debug("processFile " + source.getAbsolutePath() + " => " + target.getAbsolutePath());
038    
039            ZipInputStream sourceStream = new ZipInputStream(IOUtil.openInputStream(source, BUFFER_SIZE));
040            ZipOutputStream targetStream = new ZipOutputStream(IOUtil.openOutputStream(target, BUFFER_SIZE));
041    
042            targetStream.setLevel(Deflater.BEST_SPEED);
043    
044            try {
045                processStreams(transformer, options, sourceStream, targetStream);
046            }
047            catch (Exception e) {
048                throw new JRatException("error injecting " + source.getAbsoluteFile() + " => "
049                        + target.getAbsolutePath(), e);
050            }
051            finally {
052                IOUtil.close(sourceStream);
053                IOUtil.close(targetStream);
054            }
055        }
056    
057    
058        protected boolean processStreams(
059                Transformer transformer, InjectorOptions options, ZipInputStream sourceStream, ZipOutputStream targetStream)
060                throws Exception {
061    
062            Assert.assertNotNull("transformer", transformer);
063    
064            ZipEntry inEntry = null;
065            ZipEntry outEntry = null;
066            InputStream entryInputStream;
067    
068            addReadmeCommentFile(targetStream);
069    
070            while ((inEntry = sourceStream.getNextEntry()) != null) {
071                outEntry = new ZipEntry(inEntry.getName());
072                entryInputStream = new OpenInputStream(sourceStream);
073    
074                targetStream.putNextEntry(outEntry);
075    
076                String ext = getNormalizedExtention(inEntry);
077    
078                if (isArchiveExtention(ext)) {
079                    LOG.info("Entering nested archive : " + outEntry.getName());
080    
081                    ZipInputStream nestedSourceStream = new ZipInputStream(entryInputStream);
082                    ZipOutputStream nestedTargetStream = new ZipOutputStream(targetStream);
083    
084                    processStreams(transformer, options, nestedSourceStream, nestedTargetStream);
085                    nestedTargetStream.finish();    // this line seems important...
086    
087                    // :)
088                } else if (isClassExtention(ext)) {
089                    LOG.debug("injecting " + inEntry.getName());
090    
091                    byte[] transformedClass = transformer.inject(entryInputStream, inEntry.getName(), options);
092    
093                    targetStream.write(transformedClass);
094                } else {
095                    LOG.debug("copying " + inEntry.getName());
096                    IOUtil.copy(entryInputStream, targetStream);
097                }
098    
099                targetStream.closeEntry();
100            }
101    
102            return true;
103        }
104    
105    
106        private void addReadmeCommentFile(ZipOutputStream zipOutputStream) {
107    
108            ZipEntry entry = new ZipEntry("_READ_ME.JRAT");
109    
110            try {
111                zipOutputStream.putNextEntry(entry);
112    
113                PrintStream printStream = new PrintStream(zipOutputStream);
114    
115                printStream.println("# This Archive file was injected by");
116                printStream.println("# Shiftone JRat the Java Runtime Analysis Toolkit");
117                printStream.println("# For more information, visit http://jrat.sourceforge.net");
118                printStream.println("#\tVersion  : " + VersionUtil.getVersion());
119                printStream.println("#\tBuilt On : " + VersionUtil.getBuiltOn());
120                printStream.println("#\tBuilt By : " + VersionUtil.getBuiltBy());
121                printStream.println();
122                printStream.println();
123                printStream.println();
124                printStream.println("# the following system properties were present during injection");
125                printStream.flush();
126                System.getProperties().store(zipOutputStream, null);
127                zipOutputStream.closeEntry();
128            }
129            catch (IOException e) {
130                throw new JRatException("unable to add comment file to archive", e);
131            }
132        }
133    
134    
135        private String getNormalizedExtention(ZipEntry entry) {
136            return IOUtil.getExtention(entry.getName());
137        }
138    
139    
140        public static boolean isArchiveExtention(String ext) {
141            return EXTENTION_MATCHER.isMatch(ext);
142        }
143    
144    
145        public boolean isClassExtention(String ext) {
146            return (ext != null) && (ext.equals("class"));
147        }
148    }