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

Print this page




  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 org.openjdk.jigsaw;
  27 
  28 import java.io.*;
  29 import java.nio.charset.Charset;
  30 import java.security.*;
  31 import java.util.*;
  32 import java.util.jar.*;
  33 import java.util.zip.*;
  34 
  35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;


  36 
  37 public final class ModuleFile {
  38     /**
  39      * Return the subdir of a section in an extracted module file.
  40      */
  41     public static String getSubdirOfSection(SectionType type) {
  42         switch (type) {
  43         case MODULE_INFO:
  44         case SIGNATURE:
  45             return ".";
  46         case CLASSES:
  47         case RESOURCES:
  48             return "classes";
  49         case NATIVE_LIBS:
  50             return "lib";
  51         case NATIVE_CMDS:
  52             return "bin";
  53         case CONFIG:
  54             return "etc";
  55         default:
  56             throw new AssertionError(type);
  57         }
  58     }
  59 















  60     public final static class Reader implements Closeable {






  61 
  62         private DataInputStream stream;
  63         private File destination;
  64         private boolean deflate;
  65         private final HashType hashtype = HashType.SHA256;
  66         private File natlibs;
  67         private File natcmds;
  68         private File configs;
  69 
  70         private static class CountingInputStream extends FilterInputStream {
  71             int count;
  72             public CountingInputStream(InputStream stream, int count) {
  73                 super(stream);
  74                 this.count = count;
  75             }






  76 
  77             public int available() throws IOException {
  78                 return count;
  79             }

  80 
  81             public boolean markSupported() {
  82                 return false;













  83             }
  84 
  85             public int read() throws IOException {
  86                 if (count == 0)
  87                     return -1;
  88                 int read = super.read();
  89                 if (-1 != read)
  90                     count--;
  91                 return read;
  92             }
  93 
  94             public int read(byte[] b, int off, int len) throws IOException {
  95                 if (count == 0)
  96                     return -1;
  97                 len = Math.min(len, count);
  98                 int read = super.read(b, off, len);
  99                 if (-1 != read)
 100                     count-=read;
 101                 return read;
 102             }
 103 
 104             public void reset() throws IOException {
 105                 throw new IOException("Can't reset this stream");
 106             }
 107 
 108             public long skip(long n) throws IOException {
 109                 if (count == 0)
 110                     return -1;
 111                 n = Math.min(n, count);
 112                 long skipped = super.skip(n);
 113                 if (n > 0)
 114                     count-=skipped;
 115                 return skipped;
 116             }
 117         }
 118 
 119         public Reader(DataInputStream stream) {
 120             // Ensure that mark/reset is supported
 121             if (stream.markSupported()) {
 122                 this.stream = stream;
 123             } else {
 124                 this.stream =
 125                     new DataInputStream(new BufferedInputStream(stream));
 126             }
 127         }
 128 
 129         private void checkHashMatch(byte[] expected, byte[] computed)
 130             throws IOException
 131         {
 132             if (!MessageDigest.isEqual(expected, computed))
 133                 throw new IOException("Expected hash "
 134                                       + hashHexString(expected)
 135                                       + " instead of "
 136                                       + hashHexString(computed));
 137         }
 138 
 139         private ModuleFileHeader fileHeader = null;
 140         private MessageDigest fileDigest = null;
 141         private MessageDigest sectionDigest = null;
 142         private DataInputStream fileIn = null;
 143         private byte[] moduleInfoBytes = null;
 144         private SignatureType moduleSignatureType = null;
 145         private byte[] moduleSignatureBytes = null;
 146         private final int MAX_SECTION_HEADER_LENGTH = 128;
 147         private List<byte[]> calculatedHashes = new ArrayList<>();
 148         private boolean extract = true;
 149 
 150         /*
 151          * Reads the MODULE_INFO section and the Signature section, if present,
 152          * but does not write any files.
 153          */
 154         public byte[] readStart() throws IOException {
 155 
 156             try {
 157                 fileDigest = getHashInstance(hashtype);
 158                 sectionDigest = getHashInstance(hashtype);
 159                 DigestInputStream dis =
 160                     new DigestInputStream(stream, fileDigest);
 161                 fileHeader = ModuleFileHeader.read(dis);
 162                 // calculate module header hash
 163                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 164                 fileHeader.write(new DataOutputStream(baos));
 165                 sectionDigest.update(baos.toByteArray());
 166                 calculatedHashes.add(sectionDigest.digest());
 167 
 168                 fileIn = new DataInputStream(dis);
 169                 if (readSection(fileIn) != SectionType.MODULE_INFO)
 170                     throw new IOException("First module-file section"
 171                                           + " is not MODULE_INFO");
 172                 assert moduleInfoBytes != null;
 173 
 174                 // Read the Signature Section, if present
 175                 readSignatureSection(fileIn, dis);
 176 
 177                 return moduleInfoBytes.clone();
 178             } catch (IOException x) {
 179                 close();
 180                 throw x;
 181             }
 182         }
 183 
 184         public void readRest() throws IOException {
 185             extract = false;
 186             readRest(null, false, null, null, null);
 187         }
 188 
 189         public void readRest(File dst, boolean deflate) throws IOException {
 190             readRest(dst, deflate, null, null, null);
 191         }
 192 
 193         public void readRest(File dst, boolean deflate, File natlibs,
 194                              File natcmds, File configs)
 195                 throws IOException
 196         {
 197             this.deflate = deflate;
 198             this.destination = dst != null ? dst.getCanonicalFile() : null;
 199             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
 200             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
 201             this.configs = configs != null ? configs : new File(destination, "etc");

 202             try {
 203                 if (extract)
 204                     Files.store(moduleInfoBytes, computeRealPath("info"));
 205                 // Module-Info and Signature, if present, have been consumed
 206 
 207                 // Read rest of file until all sections have been read
 208                 stream.mark(1);
 209                 while (-1 != stream.read()) {
 210                     stream.reset();
 211                     readSection(fileIn);
 212                     stream.mark(1);








 213                 }


















 214 
 215                 close();

 216                 byte[] fileHeaderHash = fileHeader.getHashNoClone();
 217                 checkHashMatch(fileHeaderHash, fileDigest.digest());
 218                 calculatedHashes.add(fileHeaderHash);
 219             } finally {
 220                 close();
 221             }
 222         }
 223 
 224         public byte[] getHash() throws IOException {
 225             if (null == fileHeader)
 226                 readStart();


 227             return fileHeader.getHash();
 228         }
 229 
 230         public List<byte[]> getCalculatedHashes() {
 231             return calculatedHashes;
 232         }
 233 
 234         public boolean hasSignature() throws IOException {
 235             if (null == fileHeader)
 236                 readStart();
 237             return moduleSignatureBytes != null;
 238         }
 239 
 240         public SignatureType getSignatureType() throws IOException {
 241             if (null == fileHeader)
 242                 readStart();
 243             return moduleSignatureType;
 244         }
 245 
 246         public byte[] getSignature() throws IOException {
 247             if (null == fileHeader)
 248                 readStart();
 249             return moduleSignatureBytes != null
 250                 ? moduleSignatureBytes.clone()
 251                 : null;
 252         }
 253 
 254         byte[] getSignatureNoClone() {
 255              return moduleSignatureBytes;
 256         }
 257 
 258         private JarOutputStream contentStream = null;
 259 
 260         private JarOutputStream contentStream() throws IOException {
 261             if (contentStream == null) {
 262                 if (extract) {
 263                     FileOutputStream fos
 264                         = new FileOutputStream(computeRealPath("classes"));
 265                     contentStream
 266                         = new JarOutputStream(new BufferedOutputStream(fos));
 267                 } else {
 268                     contentStream = new JarOutputStream(new NullOutputStream());
 269                 }
 270             }
 271             return contentStream;
 272         }
 273 
 274         public void close() throws IOException {
 275             try {
 276                 try {
 277                     if (contentStream != null) {
 278                         contentStream.close();
 279                         contentStream = null;
 280                     }
 281                 } finally {
 282                     if (fileIn != null) {
 283                         fileIn.close();
 284                         fileIn = null;

 285                     }
 286                 }
 287             } finally {
 288                 if (filesWriter != null) {
 289                     filesWriter.close();
 290                     filesWriter = null;
 291                 }
 292             }
 293         }
 294 
 295         public void readModule() throws IOException {
 296             extract = false;
 297             readStart();
 298             readRest();
 299         }
 300 
 301         public void readModule(File dst) throws IOException {
 302             readStart();
 303             readRest(dst, false);

 304         }
 305 
 306         private void readSignatureSection(DataInputStream stream,
 307                                           DigestInputStream dis)
 308             throws IOException
 309         {
 310 
 311             // Turn off digest computation before reading Signature Section
 312             dis.on(false);
 313 
 314             // Mark the starting position
 315             stream.mark(MAX_SECTION_HEADER_LENGTH);
 316             if (stream.read() != -1) {
 317                 stream.reset();
 318                 SectionHeader header = SectionHeader.read(stream);
 319                 if (header != null &&
 320                     header.getType() == SectionType.SIGNATURE) {
 321                     readSectionContent(header, stream);
 322                 } else {
 323                     // Revert back to the starting position
 324                     stream.reset();
 325                 }
 326             }
 327 
 328             // Turn on digest computation again
 329             dis.on(true);
 330         }
 331 
 332         private SectionType readSection(DataInputStream stream)
 333             throws IOException
 334         {
 335             SectionHeader header = SectionHeader.read(stream);
 336             readSectionContent(header, stream);
 337             return header.getType();
 338         }
 339 
 340         private void readSectionContent(SectionHeader header,
 341                                         DataInputStream stream)
 342             throws IOException
 343         {
 344             SectionType type = header.getType();
 345             Compressor compressor = header.getCompressor();
 346             int csize = header.getCSize();
 347             short subsections =
 348                 type.hasFiles() ? header.getSubsections() : 1;
 349 
 350             CountingInputStream cs = new CountingInputStream(stream, csize);
 351             sectionDigest.reset();
 352             DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
 353             DataInputStream in = new DataInputStream(dis);
 354 
 355             for (int subsection = 0; subsection < subsections; subsection++)
 356                 readFile(in, compressor, type, csize);
 357 
 358             byte[] headerHash = header.getHashNoClone();
 359             checkHashMatch(headerHash, sectionDigest.digest());
 360             if (header.getType() != SectionType.SIGNATURE) {
 361                 calculatedHashes.add(headerHash);
 362             }
 363         }
 364 
 365         public void readFile(DataInputStream in,
 366                              Compressor compressor,
 367                              SectionType type,
 368                              int csize)
 369             throws IOException
 370         {
 371             switch (compressor) {
 372             case NONE:
 373                 if (type == SectionType.MODULE_INFO) {
 374                     moduleInfoBytes = readModuleInfo(in, csize);
 375 
 376                 } else if (type == SectionType.SIGNATURE) {
 377                     // Examine the Signature header
 378                     int signatureTypeValue = (int)in.readShort();
 379                     try {
 380                         moduleSignatureType =
 381                             SignatureType.valueOf(signatureTypeValue);
 382                     } catch (IllegalArgumentException x) {
 383                         throw new IOException("Invalid signature type: " +
 384                                               signatureTypeValue);
 385                     }
 386                     int length = in.readInt();
 387                     moduleSignatureBytes = readModuleSignature(in, csize - 6);
 388                     if (length != moduleSignatureBytes.length) {
 389                         throw new IOException("Invalid Signature length");
 390                     }
 391                 } else {
 392                     readUncompressedFile(in, type, csize);
 393                 }
 394                 break;
 395             case GZIP:
 396                 readGZIPCompressedFile(in, type);
 397                 break;
 398             case PACK200_GZIP:
 399                 readClasses(
 400                     new DataInputStream(new CountingInputStream(in, csize)));
 401                 break;
 402             default:
 403                 throw new IOException("Unsupported Compressor for files: " +
 404                                       compressor);
 405             }
 406         }
 407 
 408         public void readClasses(DataInputStream in) throws IOException {
 409             unpack200gzip(in);
 410         }
 411 
 412         private File currentPath = null;
 413 
 414         private OutputStream openOutputStream(SectionType type,
 415                                               String path)
 416             throws IOException
 417         {
 418             if (!extract)
 419                 return new NullOutputStream();
 420             currentPath = null;
 421             assert type != SectionType.CLASSES;
 422             if (type == SectionType.RESOURCES)
 423                 return Files.newOutputStream(contentStream(), path);
 424             currentPath = computeRealPath(type, path);
 425             File parent = currentPath.getParentFile();
 426             if (!parent.exists())
 427                 Files.mkdirs(parent, currentPath.getName());
 428             return new BufferedOutputStream(new FileOutputStream(currentPath));
 429         }
 430 
 431         private static class NullOutputStream extends OutputStream {
 432             @Override
 433             public void write(int b) throws IOException {}
 434             @Override
 435             public void write(byte[] b) throws IOException {}
 436             @Override
 437             public void write(byte[] b, int off, int len) throws IOException {}
 438         }
 439 
 440         public void readGZIPCompressedFile(DataInputStream in,
 441                                            SectionType type)
 442             throws IOException
 443         {
 444             SubSectionFileHeader header = SubSectionFileHeader.read(in);
 445             int csize = header.getCSize();
 446 
 447             // Splice off the compressed file from input stream
 448             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 449             copyStream(new CountingInputStream(in, csize), baos, csize);
 450 
 451             byte[] compressedfile = baos.toByteArray();
 452             ByteArrayInputStream bain
 453                 = new ByteArrayInputStream(compressedfile);
 454             try (GZIPInputStream gin = new GZIPInputStream(bain);
 455                  OutputStream out = openOutputStream(type, header.getPath())) {
 456                 copyStream(gin, out);
 457             }
 458 
 459             if (extract)
 460                 postExtract(type, currentPath);
 461         }
 462 
 463         public void readUncompressedFile(DataInputStream in,
 464                                          SectionType type,
 465                                          int csize)
 466             throws IOException
 467         {
 468             assert type != SectionType.MODULE_INFO;
 469             SubSectionFileHeader header = SubSectionFileHeader.read(in);
 470             csize = header.getCSize();
 471             try (OutputStream out = openOutputStream(type, header.getPath())) {
 472                 CountingInputStream cin = new CountingInputStream(in, csize);
 473                 byte[] buf = new byte[8192];
 474                 int n;
 475                 while ((n = cin.read(buf)) >= 0)
 476                     out.write(buf, 0, n);
 477             }
 478             if (extract) {
 479                 postExtract(type, currentPath);
 480             }
 481          }
 482 
 483         public byte[] readModuleInfo(DataInputStream in, int csize)
 484             throws IOException
 485         {
 486             CountingInputStream cin = new CountingInputStream(in, csize);
 487             ByteArrayOutputStream out = new ByteArrayOutputStream();
 488             byte[] buf = new byte[8192];
 489             int n;
 490             while ((n = cin.read(buf)) >= 0)
 491                 out.write(buf, 0, n);
 492             return out.toByteArray();
 493         }
 494 
 495         public byte[] readModuleSignature(DataInputStream in, int csize)
 496             throws IOException
 497         {
 498             return readModuleInfo(in, csize); // signature has the same format
 499         }
 500 
 501         // Track files installed outside the module library. For later removal.
 502         // files are relative to the modules directory.
 503         private PrintWriter filesWriter;
 504 
 505         private void trackFiles(SectionType type, File file)
 506             throws IOException
 507         {
 508             if (file == null || file.toPath().startsWith(destination.toPath()))
 509                 return;
 510 
 511             // Lazy construction, not all modules will need this.
 512             if (filesWriter == null)
 513                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 514 
 515             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 516             filesWriter.flush();
 517         }
 518 
 519         List<IOException> remove() {
 520             return ModuleFile.Reader.remove(destination);
 521         }
 522 
 523         // Removes a module, given its module install directory
 524         static List<IOException> remove(File moduleDir) {
 525             List<IOException> excs = new ArrayList<>();


 532                                                          Charset.forName("UTF-8"));
 533                     for (String fn : filenames) {
 534                         try {
 535                             Files.delete(new File(moduleDir,
 536                                                   Files.platformSeparator(fn)));
 537                         } catch (IOException x) {
 538                             excs.add(x);
 539                         }
 540                     }
 541                 } catch (IOException x) {
 542                     excs.add(x);
 543                 }
 544             }
 545 
 546             excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
 547             return excs;
 548         }
 549 
 550         // Returns the absolute path of the given section type.
 551         private File getDirOfSection(SectionType type) {
 552             if (type == SectionType.NATIVE_LIBS)
 553                 return natlibs;
 554             else if (type == SectionType.NATIVE_CMDS)
 555                 return natcmds;
 556             else if (type == SectionType.CONFIG)
 557                 return configs;
 558 
 559             // resolve sub dir section paths against the modules directory
 560             return new File(destination, ModuleFile.getSubdirOfSection(type));
 561         }
 562 
 563         private File computeRealPath(String path) throws IOException {
 564             return resolveAndNormalize(destination, path);
 565         }
 566 
 567         private File computeRealPath(SectionType type, String storedpath)
 568             throws IOException
 569         {
 570             File sectionPath = getDirOfSection(type);
 571             File realpath = new File(sectionPath,
 572                  Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
 573 
 574             validatePath(sectionPath, realpath);
 575 
 576             // Create the parent directories if necessary
 577             File parent = realpath.getParentFile();
 578             if (!parent.exists())
 579                 Files.mkdirs(parent, realpath.getName());
 580 
 581             return realpath;
 582         }
 583 
 584         private static void markNativeCodeExecutable(SectionType type,
 585                                                      File file)
 586         {
 587             if (type == SectionType.NATIVE_CMDS
 588                 || (type == SectionType.NATIVE_LIBS
 589                     && System.getProperty("os.name").startsWith("Windows")))
 590                 {
 591                     file.setExecutable(true);
 592                 }
 593         }
 594 
 595         private void postExtract(SectionType type, File path)
 596             throws IOException
 597         {
 598             markNativeCodeExecutable(type, path);
 599             trackFiles(type, path);
 600         }
 601 
 602         private void unpack200gzip(DataInputStream in) throws IOException {
 603             GZIPInputStream gis = new GZIPInputStream(in) {
 604                     public void close() throws IOException {}
 605                 };
 606             Pack200.Unpacker unpacker = Pack200.newUnpacker();
 607             if (deflate) {
 608                 Map<String,String> p = unpacker.properties();
 609                 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
 610             }
 611             unpacker.unpack(gis, contentStream());
 612         }
 613 
 614     }
 615 
 616     private static void checkCompressor(SectionType type,
 617                                         Compressor compressor) {
 618 
 619         if ((SectionType.MODULE_INFO == type &&
 620              Compressor.NONE != compressor)
 621             || (SectionType.CLASSES == type &&
 622                 Compressor.PACK200_GZIP != compressor))
 623             throw new IllegalArgumentException(type
 624                                                + " may not use compressor "
 625                                                + compressor);
 626     }
 627 
 628     private static void checkSubsectionCount(SectionType type,
 629                                              short subsections) {
 630         if (!type.hasFiles() && subsections != 0)
 631             throw new IllegalArgumentException(type
 632                                                + " subsection count not 0: "
 633                                                + subsections);
 634         else if (type.hasFiles() && subsections == 0)
 635             throw new IllegalArgumentException(type + " subsection count is 0");
 636     }
 637 
 638     private static void copyStream(InputStream in, DataOutput out)
 639         throws IOException
 640     {
 641 
 642         byte[] buffer = new byte[1024 * 8];
 643         for (int b_read = in.read(buffer);
 644              -1 != b_read;
 645              b_read = in.read(buffer))
 646             out.write(buffer, 0, b_read);
 647     }
 648 
 649     private static void copyStream(InputStream in, OutputStream out)
 650         throws IOException
 651     {
 652         copyStream(in, (DataOutput) new DataOutputStream(out));



 653     }
 654 
 655     private static void copyStream(InputStream in, DataOutput out,
 656                                    int count)
 657         throws IOException
 658     {
 659         byte[] buffer = new byte[1024 * 8];
 660 
 661         while(count > 0) {
 662             int b_read = in.read(buffer, 0, Math.min(count, buffer.length));
 663             if (-1 == b_read)
 664                 return;
 665             out.write(buffer, 0, b_read);
 666             count-=b_read;
 667         }
 668     }
 669 
 670     private static void copyStream(InputStream in, OutputStream out,
 671                                    int count)
 672         throws IOException
 673     {
 674         copyStream(in, (DataOutput) new DataOutputStream(out), count);
 675     }
 676 
 677     private static void ensureNonNegativity(long size, String parameter) {
 678         if (size < 0)
 679             throw new IllegalArgumentException(parameter + "<0: " + size);
 680     }
 681 
 682     private static void ensureNonNull(Object reference, String parameter) {
 683         if (null == reference)
 684             throw new IllegalArgumentException(parameter + " == null");
 685     }
 686 
 687     private static void ensureMatch(int found, int expected, String field)
 688         throws IOException
 689     {
 690         if (found != expected)
 691             throw new IOException(field + " expected : "
 692                 + Integer.toHexString(expected) + " found: "
 693                 + Integer.toHexString(found));
 694     }
 695 
 696     private static void ensureShortNativePath(File path, String name)


 787 
 788         return hashLength;
 789     }
 790 
 791     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 792         throws IOException
 793     {
 794         final byte[] hash = new byte[hashLength];
 795         in.readFully(hash);
 796 
 797         return hash;
 798     }
 799 
 800     private static byte[] readHash(DataInputStream in) throws IOException {
 801         return readHashBytes(in, readHashLength(in));
 802     }
 803 
 804     private static byte[] readFileHash(DigestInputStream dis)
 805         throws IOException
 806     {
 807 
 808         DataInputStream in = new DataInputStream(dis);
 809 
 810         final short hashLength = readHashLength(in);
 811 
 812         // Turn digest computation off before reading the file hash
 813         dis.on(false);
 814         byte[] hash = readHashBytes(in, hashLength);
 815         // Turn digest computation on again afterwards.
 816         dis.on(true);
 817 
 818         return hash;
 819     }
 820 
 821     public final static class ModuleFileHeader {
 822         public static final int LENGTH_WITHOUT_HASH = 30;
 823         public static final int LENGTH =
 824             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 825 
 826         // Fields are specified as unsigned. Treat signed values as bugs.
 827         private final int magic;                // MAGIC


1039         }
1040 
1041         public void write(DataOutput out) throws IOException {
1042             out.writeShort(SubSectionType.FILE.value());
1043             out.writeInt(csize);
1044             out.writeUTF(path);
1045         }
1046 
1047         public static SubSectionFileHeader read(DataInputStream in)
1048                 throws IOException
1049         {
1050             final short type = in.readShort();
1051             ensureMatch(type, SubSectionType.FILE.value(),
1052                         "ModuleFile.SubSectionType.FILE");
1053             final int csize = in.readInt();
1054             final String path = in.readUTF();
1055 
1056             return new SubSectionFileHeader(csize, path);
1057         }
1058     }


















































1059 
1060     private static void writeHash(DataOutput out, byte[] hash)
1061             throws IOException
1062     {
1063         out.writeShort(hash.length);
1064         out.write(hash);
1065     }
1066 }


  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 org.openjdk.jigsaw;
  27 
  28 import java.io.*;
  29 import java.nio.charset.Charset;
  30 import java.security.*;
  31 import java.util.*;
  32 import java.util.jar.*;
  33 import org.openjdk.jigsaw.ModuleFileParser.Event;

  34 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
  35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
  36 import static org.openjdk.jigsaw.ModuleFileParser.Event.*;
  37 
  38 public final class ModuleFile {
  39     /**
  40      * Return the subdir of a section in an extracted module file.
  41      */
  42     public static String getSubdirOfSection(SectionType type) {
  43         switch (type) {
  44         case MODULE_INFO:
  45         case SIGNATURE:
  46             return ".";
  47         case CLASSES:
  48         case RESOURCES:
  49             return "classes";
  50         case NATIVE_LIBS:
  51             return "lib";
  52         case NATIVE_CMDS:
  53             return "bin";
  54         case CONFIG:
  55             return "etc";
  56         default:
  57             throw new AssertionError(type);
  58         }
  59     }
  60 
  61     /**
  62      * Returns a ModuleFileParser instance.
  63      *
  64      * @param   stream
  65      *          module file stream
  66      *
  67      * @return  a module file parser
  68      *
  69      * @throws  ModuleFileParserException
  70      *          If there is an error processing the underlying module file
  71      */
  72     public static ModuleFileParser newParser(InputStream stream) {
  73         return new ModuleFileParserImpl(stream);
  74     }
  75 
  76     public final static class Reader implements Closeable {
  77         private final ModuleFileParser parser;
  78         private final ModuleFileHeader fileHeader;
  79         private final byte[] moduleInfoBytes;
  80         private final SignatureType moduleSignatureType;
  81         private final byte[] moduleSignatureBytes ;
  82         private final List<byte[]> calculatedHashes;
  83 

  84         private File destination;
  85         private boolean deflate;

  86         private File natlibs;
  87         private File natcmds;
  88         private File configs;
  89 
  90         public Reader(InputStream stream) throws IOException {
  91             calculatedHashes = new ArrayList<>();
  92             parser = ModuleFile.newParser(stream);
  93             fileHeader = parser.fileHeader();
  94             calculatedHashes.add(parser.getHash());
  95             // Read the MODULE_INFO and the Signature section (if present),
  96             // but does not write any files.
  97             parser.next();
  98             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  99             copyStream(parser.getRawStream(), baos);
 100             moduleInfoBytes = baos.toByteArray();
 101             assert moduleInfoBytes != null;
 102 
 103             if (parser.next() != END_SECTION)
 104                 throw new ModuleFileParserException(
 105                         "Expected END_SECTION of module-info");
 106             calculatedHashes.add(parser.getHash());
 107 
 108             if (parser.hasNext()) {
 109                 Event event = parser.next();
 110                 if (event != END_FILE) {  // more sections
 111                     SectionHeader header = parser.getSectionHeader();
 112                     if (header.type == SIGNATURE) {
 113                         SignatureSection sh = SignatureSection.read(
 114                                 new DataInputStream(parser.getRawStream()));
 115                         moduleSignatureType = SignatureType.valueOf(sh.getSignatureType());
 116                         moduleSignatureBytes = sh.getSignature();
 117                         if (parser.next() != END_SECTION)
 118                             throw new ModuleFileParserException(
 119                                     "Expected END_SECTION of signature");
 120                         if (parser.hasNext())
 121                             parser.next();  // position parser at next event
 122                         return;
 123                     }








 124                 }









 125             }
 126             // no signature section, or possibly other sections at all.
 127             moduleSignatureBytes = null;
 128             moduleSignatureType = null;
 129         }
 130 
 131         public void extractTo(File dst) throws IOException {
 132             extractTo(dst, false);






 133         }

 134 
 135         public void extractTo(File dst, boolean deflate) throws IOException {
 136             extractTo(dst, deflate, null, null, null);





 137         }

 138 
 139         public void extractTo(File dst, boolean deflate, File natlibs,
































































 140                               File natcmds, File configs)
 141             throws IOException
 142         {
 143             this.deflate = deflate;
 144             this.destination = dst != null ? dst.getCanonicalFile() : null;
 145             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
 146             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
 147             this.configs = configs != null ? configs : new File(destination, "etc");
 148 
 149             try {

 150                 Files.store(moduleInfoBytes, computeRealPath("info"));

 151 
 152                 Event event = parser.event();
 153                 if (event == END_FILE)
 154                     return;
 155 
 156                 if (event != START_SECTION)
 157                     throw new ModuleFileParserException(
 158                                         "Expected START_SECTION, got : " + event);
 159                 // Module-Info and Signature, if present, have been consumed
 160                 do {
 161                     SectionHeader header = parser.getSectionHeader();
 162                     SectionType type = header.getType();
 163                     if (type.hasFiles()) {
 164                         while(parser.skipToNextStartSubSection()) {
 165                             readSubSection(type);
 166                         }
 167                     } else if (type == CLASSES) {
 168                         Iterator<Map.Entry<String,InputStream>> classes =
 169                                 parser.getClasses();
 170                         while (classes.hasNext()) {
 171                             Map.Entry<String,InputStream> entry = classes.next();
 172                             try (OutputStream out = openOutputStream(type, entry.getKey())) {
 173                                 copyStream(entry.getValue(), out);
 174                             }
 175                         }
 176                         // END_SECTION
 177                         parser.next();
 178                     } else {
 179                         throw new IllegalArgumentException("Unknown type: " + type);
 180                     }
 181                     byte[] sectionHash = parser.getHash();
 182                     calculatedHashes.add(sectionHash);
 183                     checkHashMatch(sectionHash, header.getHash());
 184                 } while (parser.skipToNextStartSection());
 185 
 186                 if (parser.event() != END_FILE)
 187                     throw new IOException("Expected END_FILE");
 188                 byte[] fileHeaderHash = fileHeader.getHashNoClone();
 189                 checkHashMatch(fileHeaderHash, parser.getHash());
 190                 calculatedHashes.add(fileHeaderHash);
 191             } finally {
 192                 close();
 193             }
 194         }
 195 
 196         public byte[] getModuleInfoBytes() {
 197             return moduleInfoBytes.clone();
 198         }
 199 
 200         public byte[] getHash() {
 201             return fileHeader.getHash();
 202         }
 203 
 204         public List<byte[]> getCalculatedHashes() {
 205             return calculatedHashes;
 206         }
 207 
 208         public boolean hasSignature() {


 209             return moduleSignatureBytes != null;
 210         }
 211 
 212         public SignatureType getSignatureType() {


 213             return moduleSignatureType;
 214         }
 215 
 216         public byte[] getSignature() {
 217             return moduleSignatureBytes == null ? null :
 218                    moduleSignatureBytes.clone();



 219         }
 220 
 221         /*package*/byte[] getSignatureNoClone() {
 222             return moduleSignatureBytes;
 223         }
 224 
















 225         public void close() throws IOException {
 226             try {
 227                 try {
 228                     if (contentStream != null) {
 229                         contentStream.close();
 230                         contentStream = null;
 231                     }
 232                 } finally {
 233                     /*if (parser != null) {
 234                         parser.close();
 235                         parser = null;
 236                     }*/
 237                 }

 238             } finally {
 239                 if (filesWriter != null) {
 240                     filesWriter.close();
 241                     filesWriter = null;
 242                 }
 243             }
 244         }
 245 
 246         // subsections/files (resources, libs, cmds, configs)
 247         public void readSubSection(SectionType type) throws IOException {
 248             assert type == RESOURCES || type == NATIVE_LIBS ||
 249                    type == NATIVE_CMDS || type == CONFIG;

 250 
 251             SubSectionFileHeader subHeader = parser.getSubSectionFileHeader();
 252             String path = subHeader.getPath();
 253             try (OutputStream sink = openOutputStream(type, path)) {
 254                 copyStream(parser.getContentStream(), sink);
 255             }
 256 
 257             // post processing for executable and files outside the module dir
 258             postExtract(type, currentPath);

















 259         }

 260 
 261         private JarOutputStream contentStream = null;


 262 
 263         private JarOutputStream contentStream() throws IOException {
 264             if (contentStream != null)
 265                 return contentStream;




 266 
 267             return contentStream = new JarOutputStream(
 268                     new BufferedOutputStream(
 269                         new FileOutputStream(computeRealPath("classes"))));



















 270         }

 271 















































 272         private File currentPath = null;
 273 
 274         private OutputStream openOutputStream(SectionType type, String path)

 275             throws IOException
 276         {


 277             currentPath = null;
 278             if (type == CLASSES || type == RESOURCES)
 279                 return Files.newOutputStream(contentStream(), deflate, path);

 280             currentPath = computeRealPath(type, path);
 281             File parent = currentPath.getParentFile();
 282             if (!parent.exists())
 283                 Files.mkdirs(parent, currentPath.getName());
 284             return new BufferedOutputStream(new FileOutputStream(currentPath));
 285         }
 286 
 287         private static void checkHashMatch(byte[] expected, byte[] computed)










 288             throws IOException
 289         {
 290             if (!MessageDigest.isEqual(expected, computed))
 291                 throw new IOException("Expected hash "
 292                                       + hashHexString(expected)
 293                                       + " instead of "
 294                                       + hashHexString(computed));








 295         }
 296 










































 297         // Track files installed outside the module library. For later removal.
 298         // files are relative to the modules directory.
 299         private PrintWriter filesWriter;
 300 
 301         private void trackFiles(File file)
 302             throws IOException
 303         {
 304             if (file == null || file.toPath().startsWith(destination.toPath()))
 305                 return;
 306 
 307             // Lazy construction, not all modules will need this.
 308             if (filesWriter == null)
 309                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 310 
 311             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 312             filesWriter.flush();
 313         }
 314 
 315         List<IOException> remove() {
 316             return ModuleFile.Reader.remove(destination);
 317         }
 318 
 319         // Removes a module, given its module install directory
 320         static List<IOException> remove(File moduleDir) {
 321             List<IOException> excs = new ArrayList<>();


 328                                                          Charset.forName("UTF-8"));
 329                     for (String fn : filenames) {
 330                         try {
 331                             Files.delete(new File(moduleDir,
 332                                                   Files.platformSeparator(fn)));
 333                         } catch (IOException x) {
 334                             excs.add(x);
 335                         }
 336                     }
 337                 } catch (IOException x) {
 338                     excs.add(x);
 339                 }
 340             }
 341 
 342             excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
 343             return excs;
 344         }
 345 
 346         // Returns the absolute path of the given section type.
 347         private File getDirOfSection(SectionType type) {
 348             if (type == NATIVE_LIBS)
 349                 return natlibs;
 350             else if (type == NATIVE_CMDS)
 351                 return natcmds;
 352             else if (type == CONFIG)
 353                 return configs;
 354 
 355             // resolve sub dir section paths against the modules directory
 356             return new File(destination, ModuleFile.getSubdirOfSection(type));
 357         }
 358 
 359         private File computeRealPath(String path) throws IOException {
 360             return resolveAndNormalize(destination, path);
 361         }
 362 
 363         private File computeRealPath(SectionType type, String storedpath)
 364             throws IOException
 365         {
 366             File sectionPath = getDirOfSection(type);
 367             File realpath = new File(sectionPath,
 368                  Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
 369 
 370             validatePath(sectionPath, realpath);
 371 
 372             // Create the parent directories if necessary
 373             File parent = realpath.getParentFile();
 374             if (!parent.exists())
 375                 Files.mkdirs(parent, realpath.getName());
 376 
 377             return realpath;
 378         }
 379 
 380         private static void markNativeCodeExecutable(SectionType type,
 381                                                      File file)
 382         {
 383             if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
 384                     System.getProperty("os.name").startsWith("Windows")))


 385                 file.setExecutable(true);
 386         }

 387 
 388         private void postExtract(SectionType type, File path)
 389             throws IOException
 390         {
 391             markNativeCodeExecutable(type, path);
 392             trackFiles(path);
 393         }









 394     }


 395 


 396     private static void checkCompressor(SectionType type,
 397                                         Compressor compressor) {
 398 
 399         if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
 400             (CLASSES == type && Compressor.PACK200_GZIP != compressor))


 401             throw new IllegalArgumentException(type
 402                                                + " may not use compressor "
 403                                                + compressor);
 404     }
 405 
 406     private static void checkSubsectionCount(SectionType type,
 407                                              short subsections) {
 408         if (!type.hasFiles() && subsections != 0)
 409             throw new IllegalArgumentException(type
 410                                                + " subsection count not 0: "
 411                                                + subsections);
 412         else if (type.hasFiles() && subsections == 0)
 413             throw new IllegalArgumentException(type + " subsection count is 0");
 414     }
 415 











 416     private static void copyStream(InputStream in, OutputStream out)
 417         throws IOException
 418     {
 419         byte[] buf = new byte[8192];
 420         int read;
 421         while ((read = in.read(buf)) > 0)
 422             out.write(buf, 0, read);
 423     }
 424 






















 425     private static void ensureNonNegativity(long size, String parameter) {
 426         if (size < 0)
 427             throw new IllegalArgumentException(parameter + "<0: " + size);
 428     }
 429 
 430     private static void ensureNonNull(Object reference, String parameter) {
 431         if (null == reference)
 432             throw new IllegalArgumentException(parameter + " == null");
 433     }
 434 
 435     private static void ensureMatch(int found, int expected, String field)
 436         throws IOException
 437     {
 438         if (found != expected)
 439             throw new IOException(field + " expected : "
 440                 + Integer.toHexString(expected) + " found: "
 441                 + Integer.toHexString(found));
 442     }
 443 
 444     private static void ensureShortNativePath(File path, String name)


 535 
 536         return hashLength;
 537     }
 538 
 539     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 540         throws IOException
 541     {
 542         final byte[] hash = new byte[hashLength];
 543         in.readFully(hash);
 544 
 545         return hash;
 546     }
 547 
 548     private static byte[] readHash(DataInputStream in) throws IOException {
 549         return readHashBytes(in, readHashLength(in));
 550     }
 551 
 552     private static byte[] readFileHash(DigestInputStream dis)
 553         throws IOException
 554     {

 555         DataInputStream in = new DataInputStream(dis);
 556 
 557         final short hashLength = readHashLength(in);
 558 
 559         // Turn digest computation off before reading the file hash
 560         dis.on(false);
 561         byte[] hash = readHashBytes(in, hashLength);
 562         // Turn digest computation on again afterwards.
 563         dis.on(true);
 564 
 565         return hash;
 566     }
 567 
 568     public final static class ModuleFileHeader {
 569         public static final int LENGTH_WITHOUT_HASH = 30;
 570         public static final int LENGTH =
 571             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 572 
 573         // Fields are specified as unsigned. Treat signed values as bugs.
 574         private final int magic;                // MAGIC


 786         }
 787 
 788         public void write(DataOutput out) throws IOException {
 789             out.writeShort(SubSectionType.FILE.value());
 790             out.writeInt(csize);
 791             out.writeUTF(path);
 792         }
 793 
 794         public static SubSectionFileHeader read(DataInputStream in)
 795                 throws IOException
 796         {
 797             final short type = in.readShort();
 798             ensureMatch(type, SubSectionType.FILE.value(),
 799                         "ModuleFile.SubSectionType.FILE");
 800             final int csize = in.readInt();
 801             final String path = in.readUTF();
 802 
 803             return new SubSectionFileHeader(csize, path);
 804         }
 805     }
 806 
 807     public final static class SignatureSection {
 808         private final int signatureType;   // One of FileConstants.ModuleFile.HashType
 809         private final int signatureLength; // Length of signature
 810         private final byte[] signature;    // Signature bytes
 811 
 812         public int getSignatureType() {
 813             return signatureType;
 814         }
 815 
 816         public int getSignatureLength() {
 817             return signatureLength;
 818         }
 819 
 820         public byte[] getSignature() {
 821             return signature;
 822         }
 823 
 824         public SignatureSection(int signatureType, int signatureLength,
 825                                 byte[] signature) {
 826             ensureNonNegativity(signatureLength, "signatureLength");
 827 
 828             this.signatureType = signatureType;
 829             this.signatureLength = signatureLength;
 830             this.signature = signature.clone();
 831         }
 832 
 833         public void write(DataOutput out) throws IOException {
 834             out.writeShort(signatureType);
 835             out.writeInt(signatureLength);
 836             out.write(signature);
 837         }
 838 
 839         public static SignatureSection read(DataInputStream in)
 840             throws IOException
 841         {
 842             short signatureType = in.readShort();
 843             try {
 844                 SignatureType.valueOf(signatureType);
 845             } catch (IllegalArgumentException x) {
 846                 throw new IOException("Invalid signature type: " + signatureType);
 847             }
 848             final int signatureLength = in.readInt();
 849             ensureNonNegativity(signatureLength, "signatureLength");
 850             final byte[] signature = new byte[signatureLength];
 851             in.readFully(signature);
 852             return new SignatureSection(signatureType, signatureLength,
 853                                         signature);
 854         }
 855     }
 856 
 857     private static void writeHash(DataOutput out, byte[] hash)
 858             throws IOException
 859     {
 860         out.writeShort(hash.length);
 861         out.write(hash);
 862     }
 863 }