1/*
2 * Copyright (c) 2003, 2015, 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
26package com.sun.java.util.jar.pack;
27
28import java.io.BufferedInputStream;
29import java.io.ByteArrayOutputStream;
30import java.io.File;
31import java.io.FileInputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.time.LocalDateTime;
36import java.time.ZoneOffset;
37import java.util.HashSet;
38import java.util.Set;
39import java.util.SortedMap;
40import java.util.jar.JarEntry;
41import java.util.jar.JarInputStream;
42import java.util.jar.JarOutputStream;
43import java.util.jar.Pack200;
44import java.util.zip.CRC32;
45import java.util.zip.CheckedOutputStream;
46import 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
56public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker {
57
58    public UnpackerImpl() {}
59
60
61
62    /**
63     * Get the set of options for the pack and unpack engines.
64     * @return A sorted association of option key strings to option values.
65     */
66    public SortedMap<String, String> properties() {
67        return props;
68    }
69
70    // Back-pointer to NativeUnpacker, when active.
71    Object _nunp;
72
73
74    public String toString() {
75        return Utils.getVersionString();
76    }
77
78    //Driver routines
79
80    // The unpack worker...
81    /**
82     * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally
83     * the entire buffer must be read, it may be more efficient to read the packed-stream
84     * to a file and pass the File object, in the alternate method described below.
85     * <p>
86     * Closes its input but not its output.  (The output can accumulate more elements.)
87     * @param in an InputStream.
88     * @param out a JarOutputStream.
89     * @exception IOException if an error is encountered.
90     */
91    public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException {
92        if (in == null) {
93            throw new NullPointerException("null input");
94        }
95        if (out == null) {
96            throw new NullPointerException("null output");
97        }
98        assert(Utils.currentInstance.get() == null);
99
100        try {
101            Utils.currentInstance.set(this);
102            final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
103            BufferedInputStream in0 = new BufferedInputStream(in);
104            if (Utils.isJarMagic(Utils.readMagic(in0))) {
105                if (verbose > 0)
106                    Utils.log.info("Copying unpacked JAR file...");
107                Utils.copyJarFile(new JarInputStream(in0), out);
108            } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) {
109                (new DoUnpack()).run(in0, out);
110                in0.close();
111                Utils.markJarFile(out);
112            } else {
113                try {
114                    (new NativeUnpack(this)).run(in0, out);
115                } catch (UnsatisfiedLinkError | NoClassDefFoundError ex) {
116                    // failover to java implementation
117                    (new DoUnpack()).run(in0, out);
118                }
119                in0.close();
120                Utils.markJarFile(out);
121            }
122        } finally {
123            _nunp = null;
124            Utils.currentInstance.set(null);
125        }
126    }
127
128    /**
129     * Takes an input File containing the pack file, and generates a JarOutputStream.
130     * <p>
131     * Does not close its output.  (The output can accumulate more elements.)
132     * @param in a File.
133     * @param out a JarOutputStream.
134     * @exception IOException if an error is encountered.
135     */
136    public synchronized void unpack(File in, JarOutputStream out) throws IOException {
137        if (in == null) {
138            throw new NullPointerException("null input");
139        }
140        if (out == null) {
141            throw new NullPointerException("null output");
142        }
143        // Use the stream-based implementation.
144        // %%% Reconsider if native unpacker learns to memory-map the file.
145        try (FileInputStream instr = new FileInputStream(in)) {
146            unpack(instr, out);
147        }
148        if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) {
149            in.delete();
150        }
151    }
152
153    private class DoUnpack {
154        final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
155
156        {
157            props.setInteger(Pack200.Unpacker.PROGRESS, 0);
158        }
159
160        // Here's where the bits are read from disk:
161        final Package pkg = new Package();
162
163        final boolean keepModtime
164            = Pack200.Packer.KEEP.equals(
165              props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP));
166        final boolean keepDeflateHint
167            = Pack200.Packer.KEEP.equals(
168              props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP));
169        final int modtime;
170        final boolean deflateHint;
171        {
172            if (!keepModtime) {
173                modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME);
174            } else {
175                modtime = pkg.default_modtime;
176            }
177
178            deflateHint = (keepDeflateHint) ? false :
179                props.getBoolean(java.util.jar.Pack200.Unpacker.DEFLATE_HINT);
180        }
181
182        // Checksum apparatus.
183        final CRC32 crc = new CRC32();
184        final ByteArrayOutputStream bufOut = new ByteArrayOutputStream();
185        final OutputStream crcOut = new CheckedOutputStream(bufOut, crc);
186
187        public void run(BufferedInputStream in, JarOutputStream out) throws IOException {
188            if (verbose > 0) {
189                props.list(System.out);
190            }
191            for (int seg = 1; ; seg++) {
192                unpackSegment(in, out);
193
194                // Try to get another segment.
195                if (!Utils.isPackMagic(Utils.readMagic(in)))  break;
196                if (verbose > 0)
197                    Utils.log.info("Finished segment #"+seg);
198            }
199        }
200
201        private void unpackSegment(InputStream in, JarOutputStream out) throws IOException {
202            props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"0");
203            // Process the output directory or jar output.
204            new PackageReader(pkg, in).read();
205
206            if (props.getBoolean("unpack.strip.debug"))    pkg.stripAttributeKind("Debug");
207            if (props.getBoolean("unpack.strip.compile"))  pkg.stripAttributeKind("Compile");
208            props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"50");
209            pkg.ensureAllClassFiles();
210            // Now write out the files.
211            Set<Package.Class> classesToWrite = new HashSet<>(pkg.getClasses());
212            for (Package.File file : pkg.getFiles()) {
213                String name = file.nameString;
214                JarEntry je = new JarEntry(Utils.getJarEntryName(name));
215                boolean deflate;
216
217                deflate = (keepDeflateHint)
218                          ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) ||
219                            ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0))
220                          : deflateHint;
221
222                boolean needCRC = !deflate;  // STORE mode requires CRC
223
224                if (needCRC)  crc.reset();
225                bufOut.reset();
226                if (file.isClassStub()) {
227                    Package.Class cls = file.getStubClass();
228                    assert(cls != null);
229                    new ClassWriter(cls, needCRC ? crcOut : bufOut).write();
230                    classesToWrite.remove(cls);  // for an error check
231                } else {
232                    // collect data & maybe CRC
233                    file.writeTo(needCRC ? crcOut : bufOut);
234                }
235                je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED);
236                if (needCRC) {
237                    if (verbose > 0)
238                        Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue());
239
240                    je.setMethod(JarEntry.STORED);
241                    je.setSize(bufOut.size());
242                    je.setCrc(crc.getValue());
243                }
244                if (keepModtime) {
245                    LocalDateTime ldt = LocalDateTime
246                            .ofEpochSecond(file.modtime, 0, ZoneOffset.UTC);
247                    je.setTimeLocal(ldt);
248                } else {
249                    je.setTime((long)modtime * 1000);
250                }
251                out.putNextEntry(je);
252                bufOut.writeTo(out);
253                out.closeEntry();
254                if (verbose > 0)
255                    Utils.log.info("Writing "+Utils.zeString((ZipEntry)je));
256            }
257            assert(classesToWrite.isEmpty());
258            props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"100");
259            pkg.reset();  // reset for the next segment, if any
260        }
261    }
262}
263