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

Print this page




  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 HashType hashtype;
  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;


  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             hashtype = HashType.SHA256;
 121             // Ensure that mark/reset is supported
 122             if (stream.markSupported()) {
 123                 this.stream = stream;
 124             } else {
 125                 this.stream =
 126                     new DataInputStream(new BufferedInputStream(stream));
 127             }
 128         }
 129 
 130         private void checkHashMatch(byte[] expected, byte[] computed)
 131             throws IOException
 132         {
 133             if (!MessageDigest.isEqual(expected, computed))
 134                 throw new IOException("Expected hash "
 135                                       + hashHexString(expected)
 136                                       + " instead of "
 137                                       + hashHexString(computed));
 138         }
 139 
 140         private ModuleFileHeader fileHeader = null;
 141         private MessageDigest fileDigest = null;
 142         private MessageDigest sectionDigest = null;
 143         private DataInputStream fileIn = null;
 144         private byte[] moduleInfoBytes = null;
 145         private Integer moduleSignatureType = null;
 146         private byte[] moduleSignatureBytes = null;
 147         private final int MAX_SECTION_HEADER_LENGTH = 128;
 148         private List<byte[]> calculatedHashes = new ArrayList<>();
 149         private boolean extract = true;

 150 
 151         /*
 152          * Reads the MODULE_INFO section and the Signature section, if present,
 153          * but does not write any files.
 154          */
 155         public byte[] readStart() throws IOException {
 156 
 157             try {
 158                 fileDigest = getHashInstance(hashtype);
 159                 sectionDigest = getHashInstance(hashtype);
 160                 DigestInputStream dis =
 161                     new DigestInputStream(stream, fileDigest);
 162                 fileHeader = ModuleFileHeader.read(dis);
 163                 // calculate module header hash
 164                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 165                 fileHeader.write(new DataOutputStream(baos));
 166                 sectionDigest.update(baos.toByteArray());
 167                 calculatedHashes.add(sectionDigest.digest());
 168 
 169                 fileIn = new DataInputStream(dis);
 170                 if (readSection(fileIn) != SectionType.MODULE_INFO)
 171                     throw new IOException("First module-file section"
 172                                           + " is not MODULE_INFO");
 173                 assert moduleInfoBytes != null;
 174 
 175                 // Read the Signature Section, if present
 176                 readSignatureSection(fileIn, dis);
 177 
 178                 return moduleInfoBytes.clone();
 179             } catch (IOException x) {
 180                 close();
 181                 throw x;
 182             }
 183         }
 184 
 185         public void readRest() throws IOException {
 186             extract = false;
 187             readRest(null, false, null, null, null);
 188         }
 189 
 190         public void readRest(File dst, boolean deflate) throws IOException {
 191             readRest(dst, deflate, null, null, null);
 192         }
 193 
 194         public void readRest(File dst, boolean deflate, File natlibs,
 195                              File natcmds, File configs)
 196                 throws IOException
 197         {
 198             this.deflate = deflate;
 199             this.destination = dst != null ? dst.getCanonicalFile() : null;
 200             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
 201             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
 202             this.configs = configs != null ? configs : new File(destination, "etc");


 203             try {
 204                 if (extract)
 205                     Files.store(moduleInfoBytes, computeRealPath("info"));

 206                 // Module-Info and Signature, if present, have been consumed
 207 
 208                 // Read rest of file until all sections have been read
 209                 stream.mark(1);
 210                 while (-1 != stream.read()) {
 211                     stream.reset();
 212                     readSection(fileIn);
 213                     stream.mark(1);
 214                 }
 215 
 216                 close();
 217                 byte[] fileHeaderHash = fileHeader.getHashNoClone();
 218                 checkHashMatch(fileHeaderHash, fileDigest.digest());
 219                 calculatedHashes.add(fileHeaderHash);
 220             } finally {
 221                 close();
 222             }
 223         }
 224 
 225         public byte[] getHash() throws IOException {
 226             if (null == fileHeader)
 227                 readStart();
 228             return fileHeader.getHash();
 229         }
 230 


 239         }
 240 
 241         public Integer getSignatureType() throws IOException {
 242             if (null == fileHeader)
 243                 readStart();
 244             return moduleSignatureType;
 245         }
 246 
 247         public byte[] getSignature() throws IOException {
 248             if (null == fileHeader)
 249                 readStart();
 250             return moduleSignatureBytes != null
 251                 ? moduleSignatureBytes.clone()
 252                 : null;
 253         }
 254 
 255         byte[] getSignatureNoClone() {
 256             return moduleSignatureBytes;
 257         }
 258 
 259         private JarOutputStream contentStream = null;
 260 
 261         private JarOutputStream contentStream() throws IOException {
 262             if (contentStream == null) {
 263                 if (extract) {
 264                     FileOutputStream fos
 265                         = new FileOutputStream(computeRealPath("classes"));
 266                     contentStream
 267                         = new JarOutputStream(new BufferedOutputStream(fos));
 268                 } else {
 269                     contentStream = new JarOutputStream(new NullOutputStream());
 270                 }
 271             }
 272             return contentStream;
 273         }
 274 
 275         public void close() throws IOException {
 276             try {
 277                 try {
 278                     if (contentStream != null) {
 279                         contentStream.close();
 280                         contentStream = null;
 281                     }
 282                 } finally {
 283                     if (fileIn != null) {
 284                         fileIn.close();
 285                         fileIn = null;
 286                     }
 287                 }
 288             } finally {
 289                 if (filesWriter != null) {
 290                     filesWriter.close();
 291                     filesWriter = null;
 292                 }
 293             }
 294         }
 295 
 296         public void readModule() throws IOException {
 297             extract = false;
 298             readStart();
 299             readRest();
 300         }
 301 
 302         public void readModule(File dst) throws IOException {
 303             readStart();
 304             readRest(dst, false);
 305         }
 306 
 307         private void readSignatureSection(DataInputStream stream,
 308                                           DigestInputStream dis)
 309             throws IOException
 310         {
 311 
 312             // Turn off digest computation before reading Signature Section
 313             dis.on(false);
 314 
 315             // Mark the starting position
 316             stream.mark(MAX_SECTION_HEADER_LENGTH);
 317             if (stream.read() != -1) {
 318                 stream.reset();
 319                 SectionHeader header = SectionHeader.read(stream);
 320                 if (header != null &&
 321                     header.getType() == SectionType.SIGNATURE) {
 322                     readSectionContent(header, stream);
 323                 } else {
 324                     // Revert back to the starting position
 325                     stream.reset();
 326                 }
 327             }
 328 
 329             // Turn on digest computation again
 330             dis.on(true);
 331         }
 332 
 333         private SectionType readSection(DataInputStream stream)
 334             throws IOException
 335         {
 336             SectionHeader header = SectionHeader.read(stream);
 337             readSectionContent(header, stream);
 338             return header.getType();
 339         }
 340 
 341         private void readSectionContent(SectionHeader header,
 342                                         DataInputStream stream)
 343             throws IOException
 344         {
 345             SectionType type = header.getType();
 346             Compressor compressor = header.getCompressor();
 347             int csize = header.getCSize();
 348             short subsections =
 349                 type.hasFiles() ? header.getSubsections() : 1;
 350 
 351             CountingInputStream cs = new CountingInputStream(stream, csize);
 352             sectionDigest.reset();
 353             DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
 354             DataInputStream in = new DataInputStream(dis);
 355 


 356             for (int subsection = 0; subsection < subsections; subsection++)
 357                 readFile(in, compressor, type, csize);





 358 




 359             byte[] headerHash = header.getHashNoClone();
 360             checkHashMatch(headerHash, sectionDigest.digest());
 361             if (header.getType() != SectionType.SIGNATURE) {
 362                 calculatedHashes.add(headerHash);
 363             }
 364         }
 365 
 366         public void readFile(DataInputStream in,
 367                              Compressor compressor,
 368                              SectionType type,
 369                              int csize)
 370             throws IOException
 371         {
































































 372             switch (compressor) {
 373             case NONE:
 374                 if (type == SectionType.MODULE_INFO) {
 375                     moduleInfoBytes = readModuleInfo(in, csize);







 376 
 377                 } else if (type == SectionType.SIGNATURE) {
 378                     // Examine the Signature header
 379                     moduleSignatureType = (int)in.readShort();
 380                     int length = in.readInt();
 381                     moduleSignatureBytes = readModuleSignature(in, csize - 6);
 382                     if (length != moduleSignatureBytes.length) {
 383                         throw new IOException("Invalid Signature length");
 384                     }
 385                 } else {
 386                     readUncompressedFile(in, type, csize);
 387                 }
 388                 break;
 389             case GZIP:
 390                 readGZIPCompressedFile(in, type);
 391                 break;








 392             case PACK200_GZIP:
 393                 readClasses(
 394                     new DataInputStream(new CountingInputStream(in, csize)));
 395                 break;
 396             default:
 397                 throw new IOException("Unsupported Compressor for files: " +
 398                                       compressor);
 399             }
 400         }

 401 
 402         public void readClasses(DataInputStream in) throws IOException {
 403             unpack200gzip(in);











 404         }

 405 





































 406         private File currentPath = null;
 407 
 408         private OutputStream openOutputStream(SectionType type,
 409                                               String path)
 410             throws IOException
 411         {
 412             if (!extract)
 413                 return new NullOutputStream();

 414             currentPath = null;
 415             assert type != SectionType.CLASSES;
 416             if (type == SectionType.RESOURCES)
 417                 return Files.newOutputStream(contentStream(), path);
 418             currentPath = computeRealPath(type, path);
 419             File parent = currentPath.getParentFile();
 420             if (!parent.exists())
 421                 Files.mkdirs(parent, currentPath.getName());
 422             return new BufferedOutputStream(new FileOutputStream(currentPath));
 423         }
 424 
 425         private static class NullOutputStream extends OutputStream {
 426             @Override
 427             public void write(int b) throws IOException {}
 428             @Override
 429             public void write(byte[] b) throws IOException {}
 430             @Override
 431             public void write(byte[] b, int off, int len) throws IOException {}
 432         }
 433 
 434         public void readGZIPCompressedFile(DataInputStream in,
 435                                            SectionType type)
 436             throws IOException
 437         {
 438             SubSectionFileHeader header = SubSectionFileHeader.read(in);
 439             int csize = header.getCSize();
 440 
 441             // Splice off the compressed file from input stream
 442             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 443             copyStream(new CountingInputStream(in, csize), baos, csize);
 444 
 445             byte[] compressedfile = baos.toByteArray();
 446             ByteArrayInputStream bain
 447                 = new ByteArrayInputStream(compressedfile);
 448             try (GZIPInputStream gin = new GZIPInputStream(bain);
 449                  OutputStream out = openOutputStream(type, header.getPath())) {
 450                 copyStream(gin, out);
 451             }
 452 
 453             if (extract)
 454                 postExtract(type, currentPath);
 455         }
 456 
 457         public void readUncompressedFile(DataInputStream in,
 458                                          SectionType type,
 459                                          int csize)
 460             throws IOException
 461         {
 462             assert type != SectionType.MODULE_INFO;
 463             SubSectionFileHeader header = SubSectionFileHeader.read(in);
 464             csize = header.getCSize();
 465             try (OutputStream out = openOutputStream(type, header.getPath())) {
 466                 CountingInputStream cin = new CountingInputStream(in, csize);
 467                 byte[] buf = new byte[8192];
 468                 int n;
 469                 while ((n = cin.read(buf)) >= 0)
 470                     out.write(buf, 0, n);
 471             }
 472             if (extract) {
 473                 postExtract(type, currentPath);
 474             }
 475          }
 476 
 477         public byte[] readModuleInfo(DataInputStream in, int csize)
 478             throws IOException
 479         {
 480             CountingInputStream cin = new CountingInputStream(in, csize);
 481             ByteArrayOutputStream out = new ByteArrayOutputStream();
 482             byte[] buf = new byte[8192];
 483             int n;
 484             while ((n = cin.read(buf)) >= 0)
 485                 out.write(buf, 0, n);
 486             return out.toByteArray();
 487         }
 488 
 489         public byte[] readModuleSignature(DataInputStream in, int csize)
 490             throws IOException
 491         {
 492             return readModuleInfo(in, csize); // signature has the same format
 493         }
 494 
 495         // Track files installed outside the module library. For later removal.
 496         // files are relative to the modules directory.
 497         private PrintWriter filesWriter;
 498 
 499         private void trackFiles(SectionType type, File file)
 500             throws IOException
 501         {
 502             if (file == null || file.toPath().startsWith(destination.toPath()))
 503                 return;
 504 
 505             // Lazy construction, not all modules will need this.
 506             if (filesWriter == null)
 507                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 508 
 509             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 510             filesWriter.flush();
 511         }
 512 
 513         List<IOException> remove() {
 514             return ModuleFile.Reader.remove(destination);
 515         }
 516 
 517         // Removes a module, given its module install directory
 518         static List<IOException> remove(File moduleDir) {
 519             List<IOException> excs = new ArrayList<>();


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


 769         throws IOException
 770     {
 771         if (!child.toPath().startsWith(parent.toPath()) )
 772             throw new IOException("Bogus relative path: " + child);
 773         if (child.exists()) {
 774             // conflict, for now just fail
 775             throw new IOException("File " + child + " already exists");
 776         }
 777     }
 778 
 779     private static short readHashLength(DataInputStream in) throws IOException {
 780         final short hashLength = in.readShort();
 781         ensureNonNegativity(hashLength, "hashLength");
 782 
 783         return hashLength;
 784     }
 785 
 786     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 787         throws IOException
 788     {
 789 
 790         final byte[] hash = new byte[hashLength];
 791         in.readFully(hash);
 792 
 793         return hash;
 794     }
 795 
 796     private static byte[] readHash(DataInputStream in) throws IOException {
 797         return readHashBytes(in, readHashLength(in));
 798     }
 799 
 800     private static byte[] readFileHash(DigestInputStream dis)
 801         throws IOException
 802     {
 803 
 804         DataInputStream in = new DataInputStream(dis);
 805 
 806         final short hashLength = readHashLength(in);
 807 
 808         // Turn digest computation off before reading the file hash
 809         dis.on(false);
 810         byte[] hash = readHashBytes(in, hashLength);
 811         // Turn digest computation on again afterwards.
 812         dis.on(true);
 813 
 814         return hash;
 815     }
 816 
 817     public final static class ModuleFileHeader {
 818         public static final int LENGTH_WITHOUT_HASH = 30;
 819         public static final int LENGTH =
 820             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 821 
 822         // Fields are specified as unsigned. Treat signed values as bugs.
 823         private final int magic;                // MAGIC


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














































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


  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 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
  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     public final static class Reader implements Closeable {
  62 
  63         private DataInputStream stream;
  64         private File destination;
  65         private boolean deflate;
  66         private HashType hashtype;
  67         private File natlibs;
  68         private File natcmds;
  69         private File configs;
  70 
  71         private static class CountingInputStream extends FilterInputStream {
  72             private int count;
  73             public CountingInputStream(InputStream stream, int count) {
  74                 super(stream);
  75                 this.count = count;
  76             }
  77 
  78             public int available() throws IOException {
  79                 return count;
  80             }
  81 
  82             public boolean markSupported() {
  83                 return false;
  84             }
  85 
  86             public int read() throws IOException {
  87                 if (count == 0)
  88                     return -1;
  89                 int read = super.read();
  90                 if (-1 != read)
  91                     count--;
  92                 return read;


  98                 len = Math.min(len, count);
  99                 int read = super.read(b, off, len);
 100                 if (-1 != read)
 101                     count-=read;
 102                 return read;
 103             }
 104 
 105             public void reset() throws IOException {
 106                 throw new IOException("Can't reset this stream");
 107             }
 108 
 109             public long skip(long n) throws IOException {
 110                 if (count == 0)
 111                     return -1;
 112                 n = Math.min(n, count);
 113                 long skipped = super.skip(n);
 114                 if (n > 0)
 115                     count-=skipped;
 116                 return skipped;
 117             }
 118 
 119             public void close() throws IOException {
 120                 // Do nothing, CountingInputStream is used to wrap (sub)section
 121                 // content. We never want to close the underlying stream.
 122             }
 123         }
 124 
 125         public Reader(DataInputStream stream) {
 126             hashtype = HashType.SHA256;
 127             // Ensure that mark/reset is supported
 128             if (stream.markSupported()) {
 129                 this.stream = stream;
 130             } else {
 131                 this.stream =
 132                     new DataInputStream(new BufferedInputStream(stream));
 133             }
 134         }
 135 










 136         private ModuleFileHeader fileHeader = null;
 137         private MessageDigest fileDigest = null;
 138         private MessageDigest sectionDigest = null;
 139         private DataInputStream fileIn = null;
 140         private byte[] moduleInfoBytes = null;
 141         private Integer moduleSignatureType = null;
 142         private byte[] moduleSignatureBytes = null;
 143         private final int MAX_SECTION_HEADER_LENGTH = 128;
 144         private List<byte[]> calculatedHashes = new ArrayList<>();
 145         private boolean extract = true;
 146         private List<String> contents;  // list of the module-file contents
 147 
 148         /*
 149          * Reads the MODULE_INFO section and the Signature section, if present,
 150          * but does not write any files.
 151          */
 152         public byte[] readStart() throws IOException {
 153 
 154             try {
 155                 fileDigest = getHashInstance(hashtype);
 156                 sectionDigest = getHashInstance(hashtype);
 157                 DigestInputStream dis =
 158                     new DigestInputStream(stream, fileDigest);
 159                 fileHeader = ModuleFileHeader.read(dis);
 160                 // calculate module header hash
 161                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 162                 fileHeader.write(new DataOutputStream(baos));
 163                 sectionDigest.update(baos.toByteArray());
 164                 calculatedHashes.add(sectionDigest.digest());
 165 
 166                 fileIn = new DataInputStream(dis);
 167                 if (readSection(fileIn) != MODULE_INFO)
 168                     throw new IOException("First module-file section"
 169                                           + " is not MODULE_INFO");
 170                 assert moduleInfoBytes != null;
 171 
 172                 // Read the Signature Section, if present
 173                 readSignatureSection(fileIn, dis);
 174 
 175                 return moduleInfoBytes.clone();
 176             } catch (IOException x) {
 177                 close();
 178                 throw x;
 179             }
 180         }
 181 
 182         public void readRest() throws IOException {
 183             extract = false;
 184             readRest(null, false, null, null, null);
 185         }
 186 
 187         public void readRest(File dst, boolean deflate) throws IOException {
 188             readRest(dst, deflate, null, null, null);
 189         }
 190 
 191         public void readRest(File dst, boolean deflate, File natlibs,
 192                              File natcmds, File configs)
 193                 throws IOException
 194         {
 195             this.deflate = deflate;
 196             this.destination = dst != null ? dst.getCanonicalFile() : null;
 197             this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
 198             this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
 199             this.configs = configs != null ? configs : new File(destination, "etc");
 200             contents = new ArrayList<>();
 201 
 202             try {
 203                 if (extract)
 204                     Files.store(moduleInfoBytes, computeRealPath("info"));
 205                 contents.add("module-info.class");
 206                 // Module-Info and Signature, if present, have been consumed
 207 
 208                 // Read rest of file until all sections have been read
 209                 stream.mark(1);
 210                 while (stream.read() != -1) {
 211                     stream.reset();
 212                     readSection(fileIn);
 213                     stream.mark(1);
 214                 }
 215 
 216                 close();
 217                 byte[] fileHeaderHash = fileHeader.getHashNoClone();
 218                 checkHashMatch(fileHeaderHash, fileDigest.digest());
 219                 calculatedHashes.add(fileHeaderHash);
 220             } finally {
 221                 close();
 222             }
 223         }
 224 
 225         public byte[] getHash() throws IOException {
 226             if (null == fileHeader)
 227                 readStart();
 228             return fileHeader.getHash();
 229         }
 230 


 239         }
 240 
 241         public Integer getSignatureType() throws IOException {
 242             if (null == fileHeader)
 243                 readStart();
 244             return moduleSignatureType;
 245         }
 246 
 247         public byte[] getSignature() throws IOException {
 248             if (null == fileHeader)
 249                 readStart();
 250             return moduleSignatureBytes != null
 251                 ? moduleSignatureBytes.clone()
 252                 : null;
 253         }
 254 
 255         byte[] getSignatureNoClone() {
 256             return moduleSignatureBytes;
 257         }
 258 
 259         public List<String> getContents() throws IOException {
 260             if (contents == null)
 261                 readModule();
 262             Collections.sort(contents);
 263             return contents;






 264         }



 265 
 266         public void close() throws IOException {
 267             try {
 268                 try {
 269                     if (contentStream != null) {
 270                         contentStream.close();
 271                         contentStream = null;
 272                     }
 273                 } finally {
 274                     if (fileIn != null) {
 275                         fileIn.close();
 276                         fileIn = null;
 277                     }
 278                 }
 279             } finally {
 280                 if (filesWriter != null) {
 281                     filesWriter.close();
 282                     filesWriter = null;
 283                 }
 284             }
 285         }
 286 
 287         public void readModule() throws IOException {
 288             extract = false;
 289             readStart();
 290             readRest();
 291         }
 292 
 293         public void readModule(File dst) throws IOException {
 294             readStart();
 295             readRest(dst, false);
 296         }
 297 
 298         private void readSignatureSection(DataInputStream stream,
 299                                           DigestInputStream dis)
 300             throws IOException
 301         {

 302             // Turn off digest computation before reading Signature Section
 303             dis.on(false);
 304 
 305             // Mark the starting position
 306             stream.mark(MAX_SECTION_HEADER_LENGTH);
 307             if (stream.read() != -1) {
 308                 stream.reset();
 309                 SectionHeader header = SectionHeader.read(stream);
 310                 if (header != null && header.getType() == SIGNATURE)

 311                     readSectionContent(header, stream);
 312                 else
 313                     stream.reset();  // No signature, reset back to start

 314             }

 315 
 316             // Turn on digest computation again
 317             dis.on(true);
 318         }
 319 
 320         private SectionType readSection(DataInputStream stream)
 321             throws IOException
 322         {
 323             SectionHeader header = SectionHeader.read(stream);
 324             readSectionContent(header, stream);
 325             return header.getType();
 326         }
 327 
 328         private void readSectionContent(SectionHeader header,
 329                                         DataInputStream stream)
 330             throws IOException
 331         {
 332             SectionType type = header.getType();
 333             Compressor compressor = header.getCompressor();
 334             int csize = header.getCSize();


 335 
 336             CountingInputStream cs = new CountingInputStream(stream, csize);
 337             sectionDigest.reset();
 338             DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
 339             DataInputStream in = new DataInputStream(dis);
 340 
 341             if (type.hasFiles()) {
 342                 short subsections = header.getSubsections();
 343                 for (int subsection = 0; subsection < subsections; subsection++)
 344                     readSubSection(in, compressor, type);
 345             } else if (type == CLASSES) {
 346                 readClassesContent(in, compressor);
 347             } else {
 348                 readSectionBytes(in, compressor, type);
 349             }
 350 
 351             // ## appropriate exception ??
 352             if (cs.read() != -1)
 353                 throw new IllegalArgumentException("All section content not read");
 354 
 355             byte[] headerHash = header.getHashNoClone();
 356             checkHashMatch(headerHash, sectionDigest.digest());
 357             if (header.getType() != SIGNATURE) {
 358                 calculatedHashes.add(headerHash);
 359             }
 360         }
 361 
 362         // module-info OR module signature
 363         public void readSectionBytes(DataInputStream in, Compressor compressor,
 364                                      SectionType type)

 365             throws IOException
 366         {
 367             Decompressor decompressor = Decompressor.newInstance(in, compressor);
 368             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 369             decompressor.extractTo(baos);
 370 
 371             if (type == MODULE_INFO)
 372                 moduleInfoBytes = baos.toByteArray();
 373             else if (type == SIGNATURE) {
 374                 SignatureSection sh = SignatureSection.read(new DataInputStream(
 375                         new ByteArrayInputStream(baos.toByteArray())));
 376                 moduleSignatureType = sh.getSignatureType();
 377                 moduleSignatureBytes = sh.getSignature();
 378             } else
 379                 throw new IllegalArgumentException(
 380                         "Unsupported raw bytes section type: " + type);
 381         }
 382 
 383         public void readClassesContent(DataInputStream in, Compressor compressor)
 384             throws IOException
 385         {
 386             ClassesDecompressor decompressor =
 387                     ClassesDecompressor.newInstance(in, compressor, deflate);
 388             decompressor.extractTo(contentStream());
 389         }
 390 
 391         // subsections/files (resources, libs, cmds, configs)
 392         public void readSubSection(DataInputStream in, Compressor compressor,
 393                                    SectionType type)
 394             throws IOException
 395         {
 396             assert type == RESOURCES || type == NATIVE_LIBS ||
 397                    type == NATIVE_CMDS || type == CONFIG;
 398 
 399             SubSectionFileHeader header = SubSectionFileHeader.read(in);
 400             CountingInputStream cs = new CountingInputStream(in, header.getCSize());
 401             Decompressor decompressor = Decompressor.newInstance(cs, compressor);
 402             String path = header.getPath();
 403             try (OutputStream sink = openOutputStream(type, path)) {
 404                 decompressor.extractTo(sink);
 405             }
 406 
 407             String prefix = type == RESOURCES ? "" :
 408                     getSubdirOfSection(type) + File.separator;
 409             contents.add(prefix + path);
 410             // post processing for executable and files outside the module dir
 411             if (extract)
 412                 postExtract(type, currentPath);
 413         }
 414 
 415         static class Decompressor {
 416             protected InputStream source;
 417             protected Decompressor() { }
 418             protected Decompressor(InputStream source) {
 419                 // no decompression
 420                 this.source = source;
 421             }
 422 
 423             void extractTo(OutputStream sink) throws IOException {
 424                 copyStream(source, sink);
 425             }
 426 
 427             static Decompressor newInstance(InputStream source,
 428                                             Compressor compressor)
 429                     throws IOException
 430             {
 431                 switch (compressor) {
 432                     case NONE:
 433                         return new Decompressor(source);
 434                     case GZIP:
 435                         return new GZIPDecompressor(source);
 436                     default:
 437                         throw new IllegalArgumentException(
 438                                 "Unsupported compressor type: " + compressor);
 439                 }
 440             }
 441         }
 442 
 443         static class GZIPDecompressor extends Decompressor {
 444             GZIPDecompressor(InputStream source) throws IOException {
 445                 this.source = new GZIPInputStream(source) {
 446                     public void close() throws IOException {}
 447                 };


 448             }


 449         }
 450 
 451         static abstract class ClassesDecompressor {
 452             protected InputStream source;
 453 
 454             abstract void extractTo(JarOutputStream sink) throws IOException;
 455 
 456             static ClassesDecompressor newInstance(InputStream source,
 457                                                    Compressor compressor,
 458                                                    boolean deflate)
 459                     throws IOException
 460             {
 461                 switch (compressor) {
 462                     case PACK200_GZIP:
 463                         return new Pack200GZIPDecompressor(source, deflate);


 464                     default:
 465                         throw new IllegalArgumentException(
 466                                 "Unsupported compressor type: " + compressor);
 467                 }
 468             }
 469         }
 470 
 471         static class Pack200GZIPDecompressor extends ClassesDecompressor {
 472             private Pack200.Unpacker unpacker;
 473 
 474             Pack200GZIPDecompressor(InputStream source, boolean deflate)
 475                 throws IOException
 476             {
 477                 this.source = new GZIPInputStream(source) {
 478                     public void close() throws IOException {}
 479                 };
 480                 unpacker = Pack200.newUnpacker();
 481                 if (deflate) {
 482                     Map<String,String> p = unpacker.properties();
 483                     p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
 484                 }
 485             }
 486 
 487             void extractTo(JarOutputStream sink) throws IOException {
 488                 unpacker.unpack(source, sink);
 489             }
 490         }
 491 
 492         private TrackingJarOutputStream contentStream = null;
 493 
 494         // Used to retrieved archive contents
 495         private static class TrackingJarOutputStream extends JarOutputStream {
 496             private final List<String> contents;
 497             TrackingJarOutputStream(OutputStream out, List<String> contents)
 498                 throws IOException
 499             {
 500                 super(out);
 501                 this.contents = contents;
 502             }
 503 
 504             public void putNextEntry(ZipEntry ze) throws IOException {
 505                 super.putNextEntry(ze);
 506                 contents.add(ze.getName());
 507             }
 508         }
 509 
 510         private JarOutputStream contentStream() throws IOException {
 511             if (contentStream != null)
 512                 return contentStream;
 513 
 514             OutputStream sink;
 515             if (extract)
 516                 sink = new BufferedOutputStream(
 517                            new FileOutputStream(computeRealPath("classes")));
 518             else
 519                 sink = new NullOutputStream();
 520 
 521             return contentStream = new TrackingJarOutputStream(sink, contents);
 522         }
 523 
 524         private File currentPath = null;
 525 
 526         private OutputStream openOutputStream(SectionType type, String path)

 527             throws IOException
 528         {
 529             if (!extract)
 530                 return new NullOutputStream();
 531 
 532             currentPath = null;
 533             assert type != CLASSES;
 534             if (type == RESOURCES)
 535                 return Files.newOutputStream(contentStream(), deflate, path);
 536             currentPath = computeRealPath(type, path);
 537             File parent = currentPath.getParentFile();
 538             if (!parent.exists())
 539                 Files.mkdirs(parent, currentPath.getName());
 540             return new BufferedOutputStream(new FileOutputStream(currentPath));
 541         }
 542 
 543         private static class NullOutputStream extends OutputStream {
 544             @Override
 545             public void write(int b) throws IOException {}
 546             @Override
 547             public void write(byte[] b) throws IOException {}
 548             @Override
 549             public void write(byte[] b, int off, int len) throws IOException {}
 550         }
 551 
 552         private static void checkHashMatch(byte[] expected, byte[] computed)

 553             throws IOException
 554         {
 555             if (!MessageDigest.isEqual(expected, computed))
 556                 throw new IOException("Expected hash "
 557                                       + hashHexString(expected)
 558                                       + " instead of "
 559                                       + hashHexString(computed));








 560         }
 561 










































 562         // Track files installed outside the module library. For later removal.
 563         // files are relative to the modules directory.
 564         private PrintWriter filesWriter;
 565 
 566         private void trackFiles(File file)
 567             throws IOException
 568         {
 569             if (file == null || file.toPath().startsWith(destination.toPath()))
 570                 return;
 571 
 572             // Lazy construction, not all modules will need this.
 573             if (filesWriter == null)
 574                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 575 
 576             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 577             filesWriter.flush();
 578         }
 579 
 580         List<IOException> remove() {
 581             return ModuleFile.Reader.remove(destination);
 582         }
 583 
 584         // Removes a module, given its module install directory
 585         static List<IOException> remove(File moduleDir) {
 586             List<IOException> excs = new ArrayList<>();


 593                                                          Charset.forName("UTF-8"));
 594                     for (String fn : filenames) {
 595                         try {
 596                             Files.delete(new File(moduleDir,
 597                                                   Files.platformSeparator(fn)));
 598                         } catch (IOException x) {
 599                             excs.add(x);
 600                         }
 601                     }
 602                 } catch (IOException x) {
 603                     excs.add(x);
 604                 }
 605             }
 606 
 607             excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
 608             return excs;
 609         }
 610 
 611         // Returns the absolute path of the given section type.
 612         private File getDirOfSection(SectionType type) {
 613             if (type == NATIVE_LIBS)
 614                 return natlibs;
 615             else if (type == NATIVE_CMDS)
 616                 return natcmds;
 617             else if (type == CONFIG)
 618                 return configs;
 619 
 620             // resolve sub dir section paths against the modules directory
 621             return new File(destination, ModuleFile.getSubdirOfSection(type));
 622         }
 623 
 624         private File computeRealPath(String path) throws IOException {
 625             return resolveAndNormalize(destination, path);
 626         }
 627 
 628         private File computeRealPath(SectionType type, String storedpath)
 629             throws IOException
 630         {
 631             File sectionPath = getDirOfSection(type);
 632             File realpath = new File(sectionPath,
 633                  Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
 634 
 635             validatePath(sectionPath, realpath);
 636 
 637             // Create the parent directories if necessary
 638             File parent = realpath.getParentFile();
 639             if (!parent.exists())
 640                 Files.mkdirs(parent, realpath.getName());
 641 
 642             return realpath;
 643         }
 644 
 645         private static void markNativeCodeExecutable(SectionType type,
 646                                                      File file)
 647         {
 648             if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
 649                     System.getProperty("os.name").startsWith("Windows")))


 650                 file.setExecutable(true);
 651         }

 652 
 653         private void postExtract(SectionType type, File path)
 654             throws IOException
 655         {
 656             markNativeCodeExecutable(type, path);
 657             trackFiles(path);
 658         }









 659     }


 660 


 661     private static void checkCompressor(SectionType type,
 662                                         Compressor compressor) {
 663 
 664         if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
 665             (CLASSES == type && Compressor.PACK200_GZIP != compressor))


 666             throw new IllegalArgumentException(type
 667                                                + " may not use compressor "
 668                                                + compressor);
 669     }
 670 
 671     private static void checkSubsectionCount(SectionType type,
 672                                              short subsections) {
 673         if (!type.hasFiles() && subsections != 0)
 674             throw new IllegalArgumentException(type
 675                                                + " subsection count not 0: "
 676                                                + subsections);
 677         else if (type.hasFiles() && subsections == 0)
 678             throw new IllegalArgumentException(type + " subsection count is 0");
 679     }
 680 
 681     private static void copyStream(InputStream source, DataOutput sink)
 682         throws IOException
 683     {
 684         byte[] buf = new byte[8192];
 685         int b_read = 0;
 686         while((b_read = source.read(buf)) > 0)
 687             sink.write(buf, 0, b_read);


 688     }
 689 
 690     private static void copyStream(InputStream source, OutputStream sink)
 691         throws IOException
 692     {
 693         copyStream(source, (DataOutput) new DataOutputStream(sink));
 694     }
 695 






















 696     private static void ensureNonNegativity(long size, String parameter) {
 697         if (size < 0)
 698             throw new IllegalArgumentException(parameter + "<0: " + size);
 699     }
 700 
 701     private static void ensureNonNull(Object reference, String parameter) {
 702         if (null == reference)
 703             throw new IllegalArgumentException(parameter + " == null");
 704     }
 705 
 706     private static void ensureMatch(int found, int expected, String field)
 707         throws IOException
 708     {
 709         if (found != expected)
 710             throw new IOException(field + " expected : "
 711                 + Integer.toHexString(expected) + " found: "
 712                 + Integer.toHexString(found));
 713     }
 714 
 715     private static void ensureShortNativePath(File path, String name)


 794         throws IOException
 795     {
 796         if (!child.toPath().startsWith(parent.toPath()) )
 797             throw new IOException("Bogus relative path: " + child);
 798         if (child.exists()) {
 799             // conflict, for now just fail
 800             throw new IOException("File " + child + " already exists");
 801         }
 802     }
 803 
 804     private static short readHashLength(DataInputStream in) throws IOException {
 805         final short hashLength = in.readShort();
 806         ensureNonNegativity(hashLength, "hashLength");
 807 
 808         return hashLength;
 809     }
 810 
 811     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 812         throws IOException
 813     {

 814         final byte[] hash = new byte[hashLength];
 815         in.readFully(hash);
 816 
 817         return hash;
 818     }
 819 
 820     private static byte[] readHash(DataInputStream in) throws IOException {
 821         return readHashBytes(in, readHashLength(in));
 822     }
 823 
 824     private static byte[] readFileHash(DigestInputStream dis)
 825         throws IOException
 826     {

 827         DataInputStream in = new DataInputStream(dis);
 828 
 829         final short hashLength = readHashLength(in);
 830 
 831         // Turn digest computation off before reading the file hash
 832         dis.on(false);
 833         byte[] hash = readHashBytes(in, hashLength);
 834         // Turn digest computation on again afterwards.
 835         dis.on(true);
 836 
 837         return hash;
 838     }
 839 
 840     public final static class ModuleFileHeader {
 841         public static final int LENGTH_WITHOUT_HASH = 30;
 842         public static final int LENGTH =
 843             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 844 
 845         // Fields are specified as unsigned. Treat signed values as bugs.
 846         private final int magic;                // MAGIC


1060         }
1061 
1062         public void write(DataOutput out) throws IOException {
1063             out.writeShort(SubSectionType.FILE.value());
1064             out.writeInt(csize);
1065             out.writeUTF(path);
1066         }
1067 
1068         public static SubSectionFileHeader read(DataInputStream in)
1069                 throws IOException
1070         {
1071             final short type = in.readShort();
1072             ensureMatch(type, SubSectionType.FILE.value(),
1073                         "ModuleFile.SubSectionType.FILE");
1074             final int csize = in.readInt();
1075             final String path = in.readUTF();
1076 
1077             return new SubSectionFileHeader(csize, path);
1078         }
1079     }
1080 
1081     public final static class SignatureSection {
1082         private final int signatureType;   // One of FileConstants.ModuleFile.HashType
1083         private final int signatureLength; // Length of signature
1084         private final byte[] signature;    // Signature bytes
1085 
1086         public int getSignatureType() {
1087             return signatureType;
1088         }
1089 
1090         public int getSignatureLength() {
1091             return signatureLength;
1092         }
1093 
1094         public byte[] getSignature() {
1095             return signature;
1096         }
1097 
1098         public SignatureSection(int signatureType, int signatureLength,
1099                                 byte[] signature) {
1100             ensureNonNegativity(signatureLength, "signatureLength");
1101 
1102             this.signatureType = signatureType;
1103             this.signatureLength = signatureLength;
1104             this.signature = signature.clone();
1105         }
1106 
1107         public void write(DataOutput out) throws IOException {
1108             out.writeShort(signatureType);
1109             out.writeInt(signatureLength);
1110             out.write(signature);
1111         }
1112 
1113         public static SignatureSection read(DataInputStream in)
1114             throws IOException
1115         {
1116             final short signatureType = in.readShort();
1117             ensureMatch(signatureType, SignatureType.PKCS7.value(), "SignatureType.PKCS7");
1118             final int signatureLength = in.readInt();
1119             ensureNonNegativity(signatureLength, "signatureLength");
1120             final byte[] signature = new byte[signatureLength];
1121             in.readFully(signature);
1122             return new SignatureSection(signatureType, signatureLength,
1123                                         signature);
1124         }
1125     }
1126 
1127     private static void writeHash(DataOutput out, byte[] hash)
1128             throws IOException
1129     {
1130         out.writeShort(hash.length);
1131         out.write(hash);
1132     }
1133 }