src/share/classes/org/openjdk/jigsaw/ModuleFile.java

Print this page

        

@@ -31,10 +31,11 @@
 import java.util.*;
 import java.util.jar.*;
 import java.util.zip.*;
 
 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
+import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
 
 public final class ModuleFile {
     /**
      * Return the subdir of a section in an extracted module file.
      */

@@ -66,11 +67,11 @@
         private File natlibs;
         private File natcmds;
         private File configs;
 
         private static class CountingInputStream extends FilterInputStream {
-            int count;
+            private int count;
             public CountingInputStream(InputStream stream, int count) {
                 super(stream);
                 this.count = count;
             }
 

@@ -112,11 +113,16 @@
                 long skipped = super.skip(n);
                 if (n > 0)
                     count-=skipped;
                 return skipped;
             }
+
+            public void close() throws IOException {
+                // Do nothing, CountingInputStream is used to wrap (sub)section
+                // content. We never want to close the underlying stream.
         }
+        }
 
         public Reader(DataInputStream stream) {
             hashtype = HashType.SHA256;
             // Ensure that mark/reset is supported
             if (stream.markSupported()) {

@@ -125,20 +131,10 @@
                 this.stream =
                     new DataInputStream(new BufferedInputStream(stream));
             }
         }
 
-        private void checkHashMatch(byte[] expected, byte[] computed)
-            throws IOException
-        {
-            if (!MessageDigest.isEqual(expected, computed))
-                throw new IOException("Expected hash "
-                                      + hashHexString(expected)
-                                      + " instead of "
-                                      + hashHexString(computed));
-        }
-
         private ModuleFileHeader fileHeader = null;
         private MessageDigest fileDigest = null;
         private MessageDigest sectionDigest = null;
         private DataInputStream fileIn = null;
         private byte[] moduleInfoBytes = null;

@@ -145,10 +141,11 @@
         private Integer moduleSignatureType = null;
         private byte[] moduleSignatureBytes = null;
         private final int MAX_SECTION_HEADER_LENGTH = 128;
         private List<byte[]> calculatedHashes = new ArrayList<>();
         private boolean extract = true;
+        private List<String> contents;  // list of the module-file contents
 
         /*
          * Reads the MODULE_INFO section and the Signature section, if present,
          * but does not write any files.
          */

@@ -165,11 +162,11 @@
                 fileHeader.write(new DataOutputStream(baos));
                 sectionDigest.update(baos.toByteArray());
                 calculatedHashes.add(sectionDigest.digest());
 
                 fileIn = new DataInputStream(dis);
-                if (readSection(fileIn) != SectionType.MODULE_INFO)
+                if (readSection(fileIn) != MODULE_INFO)
                     throw new IOException("First module-file section"
                                           + " is not MODULE_INFO");
                 assert moduleInfoBytes != null;
 
                 // Read the Signature Section, if present

@@ -198,18 +195,21 @@
             this.deflate = deflate;
             this.destination = dst != null ? dst.getCanonicalFile() : null;
             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
             this.configs = configs != null ? configs : new File(destination, "etc");
+            contents = new ArrayList<>();
+
             try {
                 if (extract)
                     Files.store(moduleInfoBytes, computeRealPath("info"));
+                contents.add("module-info.class");
                 // Module-Info and Signature, if present, have been consumed
 
                 // Read rest of file until all sections have been read
                 stream.mark(1);
-                while (-1 != stream.read()) {
+                while (stream.read() != -1) {
                     stream.reset();
                     readSection(fileIn);
                     stream.mark(1);
                 }
 

@@ -254,25 +254,16 @@
 
         byte[] getSignatureNoClone() {
             return moduleSignatureBytes;
         }
 
-        private JarOutputStream contentStream = null;
-
-        private JarOutputStream contentStream() throws IOException {
-            if (contentStream == null) {
-                if (extract) {
-                    FileOutputStream fos
-                        = new FileOutputStream(computeRealPath("classes"));
-                    contentStream
-                        = new JarOutputStream(new BufferedOutputStream(fos));
-                } else {
-                    contentStream = new JarOutputStream(new NullOutputStream());
+        public List<String> getContents() throws IOException {
+            if (contents == null)
+                readModule();
+            Collections.sort(contents);
+            return contents;
                 }
-            }
-            return contentStream;
-        }
 
         public void close() throws IOException {
             try {
                 try {
                     if (contentStream != null) {

@@ -306,27 +297,23 @@
 
         private void readSignatureSection(DataInputStream stream,
                                           DigestInputStream dis)
             throws IOException
         {
-
             // Turn off digest computation before reading Signature Section
             dis.on(false);
 
             // Mark the starting position
             stream.mark(MAX_SECTION_HEADER_LENGTH);
             if (stream.read() != -1) {
                 stream.reset();
                 SectionHeader header = SectionHeader.read(stream);
-                if (header != null &&
-                    header.getType() == SectionType.SIGNATURE) {
+                if (header != null && header.getType() == SIGNATURE)
                     readSectionContent(header, stream);
-                } else {
-                    // Revert back to the starting position
-                    stream.reset();
+                else
+                    stream.reset();  // No signature, reset back to start
                 }
-            }
 
             // Turn on digest computation again
             dis.on(true);
         }
 

@@ -343,80 +330,211 @@
             throws IOException
         {
             SectionType type = header.getType();
             Compressor compressor = header.getCompressor();
             int csize = header.getCSize();
-            short subsections =
-                type.hasFiles() ? header.getSubsections() : 1;
 
             CountingInputStream cs = new CountingInputStream(stream, csize);
             sectionDigest.reset();
             DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
             DataInputStream in = new DataInputStream(dis);
 
+            if (type.hasFiles()) {
+                short subsections = header.getSubsections();
             for (int subsection = 0; subsection < subsections; subsection++)
-                readFile(in, compressor, type, csize);
+                    readSubSection(in, compressor, type);
+            } else if (type == CLASSES) {
+                readClassesContent(in, compressor);
+            } else {
+                readSectionBytes(in, compressor, type);
+            }
 
+            // ## appropriate exception ??
+            if (cs.read() != -1)
+                throw new IllegalArgumentException("All section content not read");
+
             byte[] headerHash = header.getHashNoClone();
             checkHashMatch(headerHash, sectionDigest.digest());
-            if (header.getType() != SectionType.SIGNATURE) {
+            if (header.getType() != SIGNATURE) {
                 calculatedHashes.add(headerHash);
             }
         }
 
-        public void readFile(DataInputStream in,
-                             Compressor compressor,
-                             SectionType type,
-                             int csize)
+        // module-info OR module signature
+        public void readSectionBytes(DataInputStream in, Compressor compressor,
+                                     SectionType type)
             throws IOException
         {
+            Decompressor decompressor = Decompressor.newInstance(in, compressor);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            decompressor.extractTo(baos);
+
+            if (type == MODULE_INFO)
+                moduleInfoBytes = baos.toByteArray();
+            else if (type == SIGNATURE) {
+                SignatureSection sh = SignatureSection.read(new DataInputStream(
+                        new ByteArrayInputStream(baos.toByteArray())));
+                moduleSignatureType = sh.getSignatureType();
+                moduleSignatureBytes = sh.getSignature();
+            } else
+                throw new IllegalArgumentException(
+                        "Unsupported raw bytes section type: " + type);
+        }
+
+        public void readClassesContent(DataInputStream in, Compressor compressor)
+            throws IOException
+        {
+            ClassesDecompressor decompressor =
+                    ClassesDecompressor.newInstance(in, compressor, deflate);
+            decompressor.extractTo(contentStream());
+        }
+
+        // subsections/files (resources, libs, cmds, configs)
+        public void readSubSection(DataInputStream in, Compressor compressor,
+                                   SectionType type)
+            throws IOException
+        {
+            assert type == RESOURCES || type == NATIVE_LIBS ||
+                   type == NATIVE_CMDS || type == CONFIG;
+
+            SubSectionFileHeader header = SubSectionFileHeader.read(in);
+            CountingInputStream cs = new CountingInputStream(in, header.getCSize());
+            Decompressor decompressor = Decompressor.newInstance(cs, compressor);
+            String path = header.getPath();
+            try (OutputStream sink = openOutputStream(type, path)) {
+                decompressor.extractTo(sink);
+            }
+
+            String prefix = type == RESOURCES ? "" :
+                    getSubdirOfSection(type) + File.separator;
+            contents.add(prefix + path);
+            // post processing for executable and files outside the module dir
+            if (extract)
+                postExtract(type, currentPath);
+        }
+
+        static class Decompressor {
+            protected InputStream source;
+            protected Decompressor() { }
+            protected Decompressor(InputStream source) {
+                // no decompression
+                this.source = source;
+            }
+
+            void extractTo(OutputStream sink) throws IOException {
+                copyStream(source, sink);
+            }
+
+            static Decompressor newInstance(InputStream source,
+                                            Compressor compressor)
+                    throws IOException
+            {
             switch (compressor) {
             case NONE:
-                if (type == SectionType.MODULE_INFO) {
-                    moduleInfoBytes = readModuleInfo(in, csize);
+                        return new Decompressor(source);
+                    case GZIP:
+                        return new GZIPDecompressor(source);
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unsupported compressor type: " + compressor);
+                }
+            }
+        }
 
-                } else if (type == SectionType.SIGNATURE) {
-                    // Examine the Signature header
-                    moduleSignatureType = (int)in.readShort();
-                    int length = in.readInt();
-                    moduleSignatureBytes = readModuleSignature(in, csize - 6);
-                    if (length != moduleSignatureBytes.length) {
-                        throw new IOException("Invalid Signature length");
+        static class GZIPDecompressor extends Decompressor {
+            GZIPDecompressor(InputStream source) throws IOException {
+                this.source = new GZIPInputStream(source) {
+                    public void close() throws IOException {}
+                };
                     }
-                } else {
-                    readUncompressedFile(in, type, csize);
                 }
-                break;
-            case GZIP:
-                readGZIPCompressedFile(in, type);
-                break;
+
+        static abstract class ClassesDecompressor {
+            protected InputStream source;
+
+            abstract void extractTo(JarOutputStream sink) throws IOException;
+
+            static ClassesDecompressor newInstance(InputStream source,
+                                                   Compressor compressor,
+                                                   boolean deflate)
+                    throws IOException
+            {
+                switch (compressor) {
             case PACK200_GZIP:
-                readClasses(
-                    new DataInputStream(new CountingInputStream(in, csize)));
-                break;
+                        return new Pack200GZIPDecompressor(source, deflate);
             default:
-                throw new IOException("Unsupported Compressor for files: " +
-                                      compressor);
+                        throw new IllegalArgumentException(
+                                "Unsupported compressor type: " + compressor);
             }
         }
+        }
 
-        public void readClasses(DataInputStream in) throws IOException {
-            unpack200gzip(in);
+        static class Pack200GZIPDecompressor extends ClassesDecompressor {
+            private Pack200.Unpacker unpacker;
+
+            Pack200GZIPDecompressor(InputStream source, boolean deflate)
+                throws IOException
+            {
+                this.source = new GZIPInputStream(source) {
+                    public void close() throws IOException {}
+                };
+                unpacker = Pack200.newUnpacker();
+                if (deflate) {
+                    Map<String,String> p = unpacker.properties();
+                    p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
         }
+            }
 
+            void extractTo(JarOutputStream sink) throws IOException {
+                unpacker.unpack(source, sink);
+            }
+        }
+
+        private TrackingJarOutputStream contentStream = null;
+
+        // Used to retrieved archive contents
+        private static class TrackingJarOutputStream extends JarOutputStream {
+            private final List<String> contents;
+            TrackingJarOutputStream(OutputStream out, List<String> contents)
+                throws IOException
+            {
+                super(out);
+                this.contents = contents;
+            }
+
+            public void putNextEntry(ZipEntry ze) throws IOException {
+                super.putNextEntry(ze);
+                contents.add(ze.getName());
+            }
+        }
+
+        private JarOutputStream contentStream() throws IOException {
+            if (contentStream != null)
+                return contentStream;
+
+            OutputStream sink;
+            if (extract)
+                sink = new BufferedOutputStream(
+                           new FileOutputStream(computeRealPath("classes")));
+            else
+                sink = new NullOutputStream();
+
+            return contentStream = new TrackingJarOutputStream(sink, contents);
+        }
+
         private File currentPath = null;
 
-        private OutputStream openOutputStream(SectionType type,
-                                              String path)
+        private OutputStream openOutputStream(SectionType type, String path)
             throws IOException
         {
             if (!extract)
                 return new NullOutputStream();
+
             currentPath = null;
-            assert type != SectionType.CLASSES;
-            if (type == SectionType.RESOURCES)
-                return Files.newOutputStream(contentStream(), path);
+            assert type != CLASSES;
+            if (type == RESOURCES)
+                return Files.newOutputStream(contentStream(), deflate, path);
             currentPath = computeRealPath(type, path);
             File parent = currentPath.getParentFile();
             if (!parent.exists())
                 Files.mkdirs(parent, currentPath.getName());
             return new BufferedOutputStream(new FileOutputStream(currentPath));

@@ -429,76 +547,25 @@
             public void write(byte[] b) throws IOException {}
             @Override
             public void write(byte[] b, int off, int len) throws IOException {}
         }
 
-        public void readGZIPCompressedFile(DataInputStream in,
-                                           SectionType type)
+        private static void checkHashMatch(byte[] expected, byte[] computed)
             throws IOException
         {
-            SubSectionFileHeader header = SubSectionFileHeader.read(in);
-            int csize = header.getCSize();
-
-            // Splice off the compressed file from input stream
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            copyStream(new CountingInputStream(in, csize), baos, csize);
-
-            byte[] compressedfile = baos.toByteArray();
-            ByteArrayInputStream bain
-                = new ByteArrayInputStream(compressedfile);
-            try (GZIPInputStream gin = new GZIPInputStream(bain);
-                 OutputStream out = openOutputStream(type, header.getPath())) {
-                copyStream(gin, out);
+            if (!MessageDigest.isEqual(expected, computed))
+                throw new IOException("Expected hash "
+                                      + hashHexString(expected)
+                                      + " instead of "
+                                      + hashHexString(computed));
             }
 
-            if (extract)
-                postExtract(type, currentPath);
-        }
-
-        public void readUncompressedFile(DataInputStream in,
-                                         SectionType type,
-                                         int csize)
-            throws IOException
-        {
-            assert type != SectionType.MODULE_INFO;
-            SubSectionFileHeader header = SubSectionFileHeader.read(in);
-            csize = header.getCSize();
-            try (OutputStream out = openOutputStream(type, header.getPath())) {
-                CountingInputStream cin = new CountingInputStream(in, csize);
-                byte[] buf = new byte[8192];
-                int n;
-                while ((n = cin.read(buf)) >= 0)
-                    out.write(buf, 0, n);
-            }
-            if (extract) {
-                postExtract(type, currentPath);
-            }
-         }
-
-        public byte[] readModuleInfo(DataInputStream in, int csize)
-            throws IOException
-        {
-            CountingInputStream cin = new CountingInputStream(in, csize);
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            byte[] buf = new byte[8192];
-            int n;
-            while ((n = cin.read(buf)) >= 0)
-                out.write(buf, 0, n);
-            return out.toByteArray();
-        }
-
-        public byte[] readModuleSignature(DataInputStream in, int csize)
-            throws IOException
-        {
-            return readModuleInfo(in, csize); // signature has the same format
-        }
-
         // Track files installed outside the module library. For later removal.
         // files are relative to the modules directory.
         private PrintWriter filesWriter;
 
-        private void trackFiles(SectionType type, File file)
+        private void trackFiles(File file)
             throws IOException
         {
             if (file == null || file.toPath().startsWith(destination.toPath()))
                 return;
 

@@ -541,15 +608,15 @@
             return excs;
         }
 
         // Returns the absolute path of the given section type.
         private File getDirOfSection(SectionType type) {
-            if (type == SectionType.NATIVE_LIBS)
+            if (type == NATIVE_LIBS)
                 return natlibs;
-            else if (type == SectionType.NATIVE_CMDS)
+            else if (type == NATIVE_CMDS)
                 return natcmds;
-            else if (type == SectionType.CONFIG)
+            else if (type == CONFIG)
                 return configs;
 
             // resolve sub dir section paths against the modules directory
             return new File(destination, ModuleFile.getSubdirOfSection(type));
         }

@@ -576,46 +643,28 @@
         }
 
         private static void markNativeCodeExecutable(SectionType type,
                                                      File file)
         {
-            if (type == SectionType.NATIVE_CMDS
-                || (type == SectionType.NATIVE_LIBS
-                    && System.getProperty("os.name").startsWith("Windows")))
-                {
+            if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
+                    System.getProperty("os.name").startsWith("Windows")))
                     file.setExecutable(true);
                 }
-        }
 
         private void postExtract(SectionType type, File path)
             throws IOException
         {
             markNativeCodeExecutable(type, path);
-            trackFiles(type, path);
+            trackFiles(path);
         }
-
-        private void unpack200gzip(DataInputStream in) throws IOException {
-            GZIPInputStream gis = new GZIPInputStream(in) {
-                    public void close() throws IOException {}
-                };
-            Pack200.Unpacker unpacker = Pack200.newUnpacker();
-            if (deflate) {
-                Map<String,String> p = unpacker.properties();
-                p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
             }
-            unpacker.unpack(gis, contentStream());
-        }
 
-    }
-
     private static void checkCompressor(SectionType type,
                                         Compressor compressor) {
 
-        if ((SectionType.MODULE_INFO == type &&
-             Compressor.NONE != compressor)
-            || (SectionType.CLASSES == type &&
-                Compressor.PACK200_GZIP != compressor))
+        if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
+            (CLASSES == type && Compressor.PACK200_GZIP != compressor))
             throw new IllegalArgumentException(type
                                                + " may not use compressor "
                                                + compressor);
     }
 

@@ -627,49 +676,25 @@
                                                + subsections);
         else if (type.hasFiles() && subsections == 0)
             throw new IllegalArgumentException(type + " subsection count is 0");
     }
 
-    private static void copyStream(InputStream in, DataOutput out)
+    private static void copyStream(InputStream source, DataOutput sink)
         throws IOException
     {
-
-        byte[] buffer = new byte[1024 * 8];
-        for (int b_read = in.read(buffer);
-             -1 != b_read;
-             b_read = in.read(buffer))
-            out.write(buffer, 0, b_read);
+        byte[] buf = new byte[8192];
+        int b_read = 0;
+        while((b_read = source.read(buf)) > 0)
+            sink.write(buf, 0, b_read);
     }
 
-    private static void copyStream(InputStream in, OutputStream out)
+    private static void copyStream(InputStream source, OutputStream sink)
         throws IOException
     {
-        copyStream(in, (DataOutput) new DataOutputStream(out));
+        copyStream(source, (DataOutput) new DataOutputStream(sink));
     }
 
-    private static void copyStream(InputStream in, DataOutput out,
-                                   int count)
-        throws IOException
-    {
-        byte[] buffer = new byte[1024 * 8];
-
-        while(count > 0) {
-            int b_read = in.read(buffer, 0, Math.min(count, buffer.length));
-            if (-1 == b_read)
-                return;
-            out.write(buffer, 0, b_read);
-            count-=b_read;
-        }
-    }
-
-    private static void copyStream(InputStream in, OutputStream out,
-                                   int count)
-        throws IOException
-    {
-        copyStream(in, (DataOutput) new DataOutputStream(out), count);
-    }
-
     private static void ensureNonNegativity(long size, String parameter) {
         if (size < 0)
             throw new IllegalArgumentException(parameter + "<0: " + size);
     }
 

@@ -784,11 +809,10 @@
     }
 
     private static byte[] readHashBytes(DataInputStream in, short hashLength)
         throws IOException
     {
-
         final byte[] hash = new byte[hashLength];
         in.readFully(hash);
 
         return hash;
     }

@@ -798,11 +822,10 @@
     }
 
     private static byte[] readFileHash(DigestInputStream dis)
         throws IOException
     {
-
         DataInputStream in = new DataInputStream(dis);
 
         final short hashLength = readHashLength(in);
 
         // Turn digest computation off before reading the file hash

@@ -1052,10 +1075,56 @@
             final String path = in.readUTF();
 
             return new SubSectionFileHeader(csize, path);
         }
     }
+
+    public final static class SignatureSection {
+        private final int signatureType;   // One of FileConstants.ModuleFile.HashType
+        private final int signatureLength; // Length of signature
+        private final byte[] signature;    // Signature bytes
+
+        public int getSignatureType() {
+            return signatureType;
+        }
+
+        public int getSignatureLength() {
+            return signatureLength;
+        }
+
+        public byte[] getSignature() {
+            return signature;
+        }
+
+        public SignatureSection(int signatureType, int signatureLength,
+                                byte[] signature) {
+            ensureNonNegativity(signatureLength, "signatureLength");
+
+            this.signatureType = signatureType;
+            this.signatureLength = signatureLength;
+            this.signature = signature.clone();
+        }
+
+        public void write(DataOutput out) throws IOException {
+            out.writeShort(signatureType);
+            out.writeInt(signatureLength);
+            out.write(signature);
+        }
+
+        public static SignatureSection read(DataInputStream in)
+            throws IOException
+        {
+            final short signatureType = in.readShort();
+            ensureMatch(signatureType, SignatureType.PKCS7.value(), "SignatureType.PKCS7");
+            final int signatureLength = in.readInt();
+            ensureNonNegativity(signatureLength, "signatureLength");
+            final byte[] signature = new byte[signatureLength];
+            in.readFully(signature);
+            return new SignatureSection(signatureType, signatureLength,
+                                        signature);
+        }
+    }
 
     private static void writeHash(DataOutput out, byte[] hash)
             throws IOException
     {
         out.writeShort(hash.length);