1 /*
   2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.java.util.jar.pack;
  27 
  28 import java.beans.PropertyChangeListener;
  29 import java.io.BufferedInputStream;
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.File;
  32 import java.io.FileInputStream;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.OutputStream;
  36 import java.util.HashSet;
  37 import java.util.Set;
  38 import java.util.SortedMap;
  39 import java.util.TimeZone;
  40 import java.util.jar.JarEntry;
  41 import java.util.jar.JarInputStream;
  42 import java.util.jar.JarOutputStream;
  43 import java.util.jar.Pack200;
  44 import java.util.zip.CRC32;
  45 import java.util.zip.CheckedOutputStream;
  46 import java.util.zip.ZipEntry;
  47 
  48 /*
  49  * Implementation of the Pack provider.
  50  * </pre></blockquote>
  51  * @author John Rose
  52  * @author Kumar Srinivasan
  53  */
  54 
  55 
  56 public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker {
  57 
  58 
  59     /**
  60      * Register a listener for changes to options.
  61      * @param listener  An object to be invoked when a property is changed.
  62      */
  63     public void addPropertyChangeListener(PropertyChangeListener listener) {
  64         props.addListener(listener);
  65     }
  66 
  67 
  68     /**
  69      * Remove a listener for the PropertyChange event.
  70      * @param listener  The PropertyChange listener to be removed.
  71      */
  72     public void removePropertyChangeListener(PropertyChangeListener listener) {
  73         props.removeListener(listener);
  74     }
  75 
  76     public UnpackerImpl() {}
  77 
  78 
  79 
  80     /**
  81      * Get the set of options for the pack and unpack engines.
  82      * @return A sorted association of option key strings to option values.
  83      */
  84     public SortedMap<String, String> properties() {
  85         return props;
  86     }
  87 
  88     // Back-pointer to NativeUnpacker, when active.
  89     Object _nunp;
  90 
  91 
  92     public String toString() {
  93         return Utils.getVersionString();
  94     }
  95 
  96     //Driver routines
  97 
  98     // The unpack worker...
  99     /**
 100      * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally
 101      * the entire buffer must be read, it may be more efficient to read the packed-stream
 102      * to a file and pass the File object, in the alternate method described below.
 103      * <p>
 104      * Closes its input but not its output.  (The output can accumulate more elements.)
 105      * @param in an InputStream.
 106      * @param out a JarOutputStream.
 107      * @exception IOException if an error is encountered.
 108      */
 109     public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException {
 110         if (in == null) {
 111             throw new NullPointerException("null input");
 112         }
 113         if (out == null) {
 114             throw new NullPointerException("null output");
 115         }
 116         assert(Utils.currentInstance.get() == null);
 117         TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE))
 118                       ? null
 119                       : TimeZone.getDefault();
 120 
 121         try {
 122             Utils.currentInstance.set(this);
 123             if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
 124             final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
 125             BufferedInputStream in0 = new BufferedInputStream(in);
 126             if (Utils.isJarMagic(Utils.readMagic(in0))) {
 127                 if (verbose > 0)
 128                     Utils.log.info("Copying unpacked JAR file...");
 129                 Utils.copyJarFile(new JarInputStream(in0), out);
 130             } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) {
 131                 (new DoUnpack()).run(in0, out);
 132                 in0.close();
 133                 Utils.markJarFile(out);
 134             } else {
 135                 try {
 136                     (new NativeUnpack(this)).run(in0, out);
 137                     in0.close();
 138                     Utils.markJarFile(out);
 139                 } catch (UnsatisfiedLinkError ule) {
 140                     // failover to java implementation
 141                     (new DoUnpack()).run(in0, out);
 142                     in0.close();
 143                     Utils.markJarFile(out);
 144                 }
 145             }
 146         } finally {
 147             _nunp = null;
 148             Utils.currentInstance.set(null);
 149             if (tz != null) TimeZone.setDefault(tz);
 150         }
 151     }
 152 
 153     /**
 154      * Takes an input File containing the pack file, and generates a JarOutputStream.
 155      * <p>
 156      * Does not close its output.  (The output can accumulate more elements.)
 157      * @param in a File.
 158      * @param out a JarOutputStream.
 159      * @exception IOException if an error is encountered.
 160      */
 161     public synchronized void unpack(File in, JarOutputStream out) throws IOException {
 162         if (in == null) {
 163             throw new NullPointerException("null input");
 164         }
 165         if (out == null) {
 166             throw new NullPointerException("null output");
 167         }
 168         // Use the stream-based implementation.
 169         // %%% Reconsider if native unpacker learns to memory-map the file.
 170         try (FileInputStream instr = new FileInputStream(in)) {
 171             unpack(instr, out);
 172         }
 173         if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) {
 174             in.delete();
 175         }
 176     }
 177 
 178     private class DoUnpack {
 179         final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
 180 
 181         {
 182             props.setInteger(Pack200.Unpacker.PROGRESS, 0);
 183         }
 184 
 185         // Here's where the bits are read from disk:
 186         final Package pkg = new Package();
 187 
 188         final boolean keepModtime
 189             = Pack200.Packer.KEEP.equals(
 190               props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP));
 191         final boolean keepDeflateHint
 192             = Pack200.Packer.KEEP.equals(
 193               props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP));
 194         final int modtime;
 195         final boolean deflateHint;
 196         {
 197             if (!keepModtime) {
 198                 modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME);
 199             } else {
 200                 modtime = pkg.default_modtime;
 201             }
 202 
 203             deflateHint = (keepDeflateHint) ? false :
 204                 props.getBoolean(java.util.jar.Pack200.Unpacker.DEFLATE_HINT);
 205         }
 206 
 207         // Checksum apparatus.
 208         final CRC32 crc = new CRC32();
 209         final ByteArrayOutputStream bufOut = new ByteArrayOutputStream();
 210         final OutputStream crcOut = new CheckedOutputStream(bufOut, crc);
 211 
 212         public void run(BufferedInputStream in, JarOutputStream out) throws IOException {
 213             if (verbose > 0) {
 214                 props.list(System.out);
 215             }
 216             for (int seg = 1; ; seg++) {
 217                 unpackSegment(in, out);
 218 
 219                 // Try to get another segment.
 220                 if (!Utils.isPackMagic(Utils.readMagic(in)))  break;
 221                 if (verbose > 0)
 222                     Utils.log.info("Finished segment #"+seg);
 223             }
 224         }
 225 
 226         private void unpackSegment(InputStream in, JarOutputStream out) throws IOException {
 227             props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"0");
 228             // Process the output directory or jar output.
 229             new PackageReader(pkg, in).read();
 230 
 231             if (props.getBoolean("unpack.strip.debug"))    pkg.stripAttributeKind("Debug");
 232             if (props.getBoolean("unpack.strip.compile"))  pkg.stripAttributeKind("Compile");
 233             props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"50");
 234             pkg.ensureAllClassFiles();
 235             // Now write out the files.
 236             Set<Package.Class> classesToWrite = new HashSet<>(pkg.getClasses());
 237             for (Package.File file : pkg.getFiles()) {
 238                 String name = file.nameString;
 239                 JarEntry je = new JarEntry(Utils.getJarEntryName(name));
 240                 boolean deflate;
 241 
 242                 deflate = (keepDeflateHint)
 243                           ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) ||
 244                             ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0))
 245                           : deflateHint;
 246 
 247                 boolean needCRC = !deflate;  // STORE mode requires CRC
 248 
 249                 if (needCRC)  crc.reset();
 250                 bufOut.reset();
 251                 if (file.isClassStub()) {
 252                     Package.Class cls = file.getStubClass();
 253                     assert(cls != null);
 254                     new ClassWriter(cls, needCRC ? crcOut : bufOut).write();
 255                     classesToWrite.remove(cls);  // for an error check
 256                 } else {
 257                     // collect data & maybe CRC
 258                     file.writeTo(needCRC ? crcOut : bufOut);
 259                 }
 260                 je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED);
 261                 if (needCRC) {
 262                     if (verbose > 0)
 263                         Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue());
 264 
 265                     je.setMethod(JarEntry.STORED);
 266                     je.setSize(bufOut.size());
 267                     je.setCrc(crc.getValue());
 268                 }
 269                 if (keepModtime) {
 270                     je.setTime(file.modtime);
 271                     // Convert back to milliseconds
 272                     je.setTime((long)file.modtime * 1000);
 273                 } else {
 274                     je.setTime((long)modtime * 1000);
 275                 }
 276                 out.putNextEntry(je);
 277                 bufOut.writeTo(out);
 278                 out.closeEntry();
 279                 if (verbose > 0)
 280                     Utils.log.info("Writing "+Utils.zeString((ZipEntry)je));
 281             }
 282             assert(classesToWrite.isEmpty());
 283             props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"100");
 284             pkg.reset();  // reset for the next segment, if any
 285         }
 286     }
 287 }