1 /* 2 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package org.openjdk.jigsaw; 27 28 import java.io.*; 29 import java.security.*; 30 import java.util.*; 31 import java.util.jar.*; 32 import java.util.zip.*; 33 34 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*; 35 36 public final class ModuleFile { 37 /** 38 * Return the subdir of a section in an extracted module file. 39 */ 40 public static String getSubdirOfSection(SectionType type) { 41 switch (type) { 42 case MODULE_INFO: 43 case SIGNATURE: 44 return "."; 45 case CLASSES: 46 case RESOURCES: 47 return "classes"; 48 case NATIVE_LIBS: 49 return "lib"; 50 case NATIVE_CMDS: 51 return "bin"; 52 case CONFIG: 53 return "etc"; 54 default: 55 throw new AssertionError(type); 56 } 57 } 58 59 public final static class Reader implements Closeable { 60 61 // The library where this module is to be installed, or null if 62 // simply extracting ( jmod Extract, or jsign ) 63 private SimpleLibrary lib; 64 private DataInputStream stream; 65 private File destination; 66 private boolean deflate; 67 private HashType hashtype; 68 69 private static class CountingInputStream extends FilterInputStream { 70 int count; 71 public CountingInputStream(InputStream stream, int count) { 72 super(stream); 73 this.count = count; 74 } 75 76 public int available() throws IOException { 77 return count; 78 } 79 80 public boolean markSupported() { 81 return false; 82 } 83 84 public int read() throws IOException { 85 if (count == 0) 86 return -1; 87 int read = super.read(); 88 if (-1 != read) 89 count--; 90 return read; 91 } 92 93 public int read(byte[] b, int off, int len) throws IOException { 94 if (count == 0) 95 return -1; 96 len = Math.min(len, count); 97 int read = super.read(b, off, len); 98 if (-1 != read) 99 count-=read; 100 return read; 101 } 102 103 public void reset() throws IOException { 104 throw new IOException("Can't reset this stream"); 105 } 106 107 public long skip(long n) throws IOException { 108 if (count == 0) 109 return -1; 110 n = Math.min(n, count); 111 long skipped = super.skip(n); 112 if (n > 0) 113 count-=skipped; 114 return skipped; 115 } 116 } 117 118 public Reader(DataInputStream stream) { 119 hashtype = HashType.SHA256; 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 public Reader(DataInputStream stream, SimpleLibrary lib) { 130 this(stream); 131 this.lib = lib; 132 } 133 134 private void checkHashMatch(byte[] expected, byte[] computed) 135 throws IOException 136 { 137 if (!MessageDigest.isEqual(expected, computed)) 138 throw new IOException("Expected hash " 139 + hashHexString(expected) 140 + " instead of " 141 + hashHexString(computed)); 142 } 143 144 private ModuleFileHeader fileHeader = null; 145 private MessageDigest fileDigest = null; 146 private MessageDigest sectionDigest = null; 147 private DataInputStream fileIn = null; 148 private byte[] moduleInfoBytes = null; 149 private Integer moduleSignatureType = null; 150 private byte[] moduleSignatureBytes = null; 151 private final int MAX_SECTION_HEADER_LENGTH = 128; 152 private List<byte[]> calculatedHashes = new ArrayList<>(); 153 private boolean extract = true; 154 155 /* 156 * Reads the MODULE_INFO section and the Signature section, if present, 157 * but does not write any files. 158 */ 159 public byte[] readStart() throws IOException { 160 161 try { 162 fileDigest = getHashInstance(hashtype); 163 sectionDigest = getHashInstance(hashtype); 164 DigestInputStream dis = 165 new DigestInputStream(stream, fileDigest); 166 fileHeader = ModuleFileHeader.read(dis); 167 // calculate module header hash 168 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 169 fileHeader.write(new DataOutputStream(baos)); 170 sectionDigest.update(baos.toByteArray()); 171 calculatedHashes.add(sectionDigest.digest()); 172 173 fileIn = new DataInputStream(dis); 174 if (readSection(fileIn) != SectionType.MODULE_INFO) 175 throw new IOException("First module-file section" 176 + " is not MODULE_INFO"); 177 assert moduleInfoBytes != null; 178 179 // Read the Signature Section, if present 180 readSignatureSection(fileIn, dis); 181 182 return moduleInfoBytes.clone(); 183 } catch (IOException x) { 184 close(); 185 throw x; 186 } 187 } 188 189 public void readRest() throws IOException { 190 extract = false; 191 readRest(null, false); 192 } 193 194 public void readRest(File dst, boolean deflate) throws IOException { 195 this.destination = dst; 196 this.deflate = deflate; 197 try { 198 if (extract) 199 Files.store(moduleInfoBytes, computeRealPath("info")); 200 // Module-Info and Signature, if present, have been consumed 201 202 // Read rest of file until all sections have been read 203 stream.mark(1); 204 while (-1 != stream.read()) { 205 stream.reset(); 206 readSection(fileIn); 207 stream.mark(1); 208 } 209 210 close(); 211 byte[] fileHeaderHash = fileHeader.getHashNoClone(); 212 checkHashMatch(fileHeaderHash, fileDigest.digest()); 213 calculatedHashes.add(fileHeaderHash); 214 } finally { 215 close(); 216 } 217 } 218 219 public byte[] getHash() throws IOException { 220 if (null == fileHeader) 221 readStart(); 222 return fileHeader.getHash(); 223 } 224 225 public List<byte[]> getCalculatedHashes() { 226 return calculatedHashes; 227 } 228 229 public boolean hasSignature() throws IOException { 230 if (null == fileHeader) 231 readStart(); 232 return moduleSignatureBytes != null; 233 } 234 235 public Integer getSignatureType() throws IOException { 236 if (null == fileHeader) 237 readStart(); 238 return moduleSignatureType; 239 } 240 241 public byte[] getSignature() throws IOException { 242 if (null == fileHeader) 243 readStart(); 244 return moduleSignatureBytes != null 245 ? moduleSignatureBytes.clone() 246 : null; 247 } 248 249 byte[] getSignatureNoClone() { 250 return moduleSignatureBytes; 251 } 252 253 private JarOutputStream contentStream = null; 254 255 private JarOutputStream contentStream() throws IOException { 256 if (contentStream == null) { 257 if (extract) { 258 FileOutputStream fos 259 = new FileOutputStream(computeRealPath("classes")); 260 contentStream 261 = new JarOutputStream(new BufferedOutputStream(fos)); 262 } else { 263 contentStream = new JarOutputStream(new NullOutputStream()); 264 } 265 } 266 return contentStream; 267 } 268 269 public void close() throws IOException { 270 try { 271 if (contentStream != null) { 272 contentStream.close(); 273 contentStream = null; 274 } 275 } finally { 276 if (fileIn != null) { 277 fileIn.close(); 278 fileIn = null; 279 } 280 } 281 } 282 283 public void readModule() throws IOException { 284 extract = false; 285 readStart(); 286 readRest(); 287 } 288 289 public void readModule(File dst) throws IOException { 290 readStart(); 291 readRest(dst, false); 292 } 293 294 private void readSignatureSection(DataInputStream stream, 295 DigestInputStream dis) 296 throws IOException 297 { 298 299 // Turn off digest computation before reading Signature Section 300 dis.on(false); 301 302 // Mark the starting position 303 stream.mark(MAX_SECTION_HEADER_LENGTH); 304 if (stream.read() != -1) { 305 stream.reset(); 306 SectionHeader header = SectionHeader.read(stream); 307 if (header != null && 308 header.getType() == SectionType.SIGNATURE) { 309 readSectionContent(header, stream); 310 } else { 311 // Revert back to the starting position 312 stream.reset(); 313 } 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 short subsections = 336 type.hasFiles() ? header.getSubsections() : 1; 337 338 CountingInputStream cs = new CountingInputStream(stream, csize); 339 sectionDigest.reset(); 340 DigestInputStream dis = new DigestInputStream(cs, sectionDigest); 341 DataInputStream in = new DataInputStream(dis); 342 343 for (int subsection = 0; subsection < subsections; subsection++) 344 readFile(in, compressor, type, csize); 345 346 byte[] headerHash = header.getHashNoClone(); 347 checkHashMatch(headerHash, sectionDigest.digest()); 348 if (header.getType() != SectionType.SIGNATURE) { 349 calculatedHashes.add(headerHash); 350 } 351 } 352 353 public void readFile(DataInputStream in, 354 Compressor compressor, 355 SectionType type, 356 int csize) 357 throws IOException 358 { 359 switch (compressor) { 360 case NONE: 361 if (type == SectionType.MODULE_INFO) { 362 moduleInfoBytes = readModuleInfo(in, csize); 363 364 } else if (type == SectionType.SIGNATURE) { 365 // Examine the Signature header 366 moduleSignatureType = (int)in.readShort(); 367 int length = in.readInt(); 368 moduleSignatureBytes = readModuleSignature(in, csize - 6); 369 if (length != moduleSignatureBytes.length) { 370 throw new IOException("Invalid Signature length"); 371 } 372 } else { 373 readUncompressedFile(in, type, csize); 374 } 375 break; 376 case GZIP: 377 readGZIPCompressedFile(in, type); 378 break; 379 case PACK200_GZIP: 380 readClasses( 381 new DataInputStream(new CountingInputStream(in, csize))); 382 break; 383 default: 384 throw new IOException("Unsupported Compressor for files: " + 385 compressor); 386 } 387 } 388 389 public void readClasses(DataInputStream in) throws IOException { 390 unpack200gzip(in); 391 } 392 393 private File currentPath = null; 394 395 private OutputStream openOutputStream(SectionType type, 396 String path) 397 throws IOException 398 { 399 if (!extract) 400 return new NullOutputStream(); 401 currentPath = null; 402 assert type != SectionType.CLASSES; 403 if (type == SectionType.RESOURCES) 404 return Files.newOutputStream(contentStream(), path); 405 currentPath = computeRealPath(type, path); 406 File parent = currentPath.getParentFile(); 407 if (!parent.exists()) 408 Files.mkdirs(parent, currentPath.getName()); 409 return new BufferedOutputStream(new FileOutputStream(currentPath)); 410 } 411 412 private static class NullOutputStream extends OutputStream { 413 @Override 414 public void write(int b) throws IOException {} 415 @Override 416 public void write(byte[] b) throws IOException {} 417 @Override 418 public void write(byte[] b, int off, int len) throws IOException {} 419 } 420 421 public void readGZIPCompressedFile(DataInputStream in, 422 SectionType type) 423 throws IOException 424 { 425 SubSectionFileHeader header = SubSectionFileHeader.read(in); 426 int csize = header.getCSize(); 427 428 // Splice off the compressed file from input stream 429 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 430 copyStream(new CountingInputStream(in, csize), baos, csize); 431 432 byte[] compressedfile = baos.toByteArray(); 433 ByteArrayInputStream bain 434 = new ByteArrayInputStream(compressedfile); 435 try (GZIPInputStream gin = new GZIPInputStream(bain); 436 OutputStream out = openOutputStream(type, header.getPath())) { 437 copyStream(gin, out); 438 } 439 440 if (extract) 441 markNativeCodeExecutable(type, currentPath); 442 } 443 444 public void readUncompressedFile(DataInputStream in, 445 SectionType type, 446 int csize) 447 throws IOException 448 { 449 assert type != SectionType.MODULE_INFO; 450 SubSectionFileHeader header = SubSectionFileHeader.read(in); 451 csize = header.getCSize(); 452 try (OutputStream out = openOutputStream(type, header.getPath())) { 453 CountingInputStream cin = new CountingInputStream(in, csize); 454 byte[] buf = new byte[8192]; 455 int n; 456 while ((n = cin.read(buf)) >= 0) 457 out.write(buf, 0, n); 458 } 459 markNativeCodeExecutable(type, currentPath); 460 } 461 462 public byte[] readModuleInfo(DataInputStream in, int csize) 463 throws IOException 464 { 465 CountingInputStream cin = new CountingInputStream(in, csize); 466 ByteArrayOutputStream out = new ByteArrayOutputStream(); 467 byte[] buf = new byte[8192]; 468 int n; 469 while ((n = cin.read(buf)) >= 0) 470 out.write(buf, 0, n); 471 return out.toByteArray(); 472 } 473 474 public byte[] readModuleSignature(DataInputStream in, int csize) 475 throws IOException 476 { 477 return readModuleInfo(in, csize); // signature has the same format 478 } 479 480 // Returns the path for the section type, if the library 481 // has one configured. 482 private File librarySectionPath(SectionType type) { 483 if (lib != null && type == SectionType.NATIVE_LIBS 484 && lib.natlibs() != null) 485 return lib.natlibs(); 486 if (lib != null && type == SectionType.NATIVE_CMDS 487 && lib.natcmds() != null) 488 return lib.natcmds(); 489 490 return null; 491 } 492 493 private File computeRealPath(String storedpath) throws IOException { 494 495 String convertedpath = storedpath.replace('/', File.separatorChar); 496 File path = new File(convertedpath); 497 498 // Absolute path names are not permitted. 499 ensureNonAbsolute(path); 500 path = resolveAndNormalize(destination, convertedpath); 501 // Create the parent directories if necessary 502 File parent = path.getParentFile(); 503 if (!parent.exists()) 504 Files.mkdirs(parent, path.getName()); 505 506 return path; 507 } 508 509 private File computeRealPath(SectionType type, 510 String storedpath) 511 throws IOException 512 { 513 File lsp = librarySectionPath(type); 514 if (lsp != null) { 515 // The library has a configured path for this section 516 File realpath = new File(lsp, storedpath); 517 // Create the parent directories if necessary 518 File parent = realpath.getParentFile(); 519 if (!parent.exists()) 520 Files.mkdirs(parent, realpath.getName()); 521 return realpath; 522 } 523 String dir = getSubdirOfSection(type); 524 return computeRealPath(dir + File.separatorChar + storedpath); 525 } 526 527 private static void markNativeCodeExecutable(SectionType type, 528 File file) 529 { 530 if (type == SectionType.NATIVE_CMDS 531 || (type == SectionType.NATIVE_LIBS 532 && System.getProperty("os.name").startsWith("Windows"))) 533 { 534 file.setExecutable(true); 535 } 536 } 537 538 private void unpack200gzip(DataInputStream in) throws IOException { 539 GZIPInputStream gis = new GZIPInputStream(in) { 540 public void close() throws IOException {} 541 }; 542 Pack200.Unpacker unpacker = Pack200.newUnpacker(); 543 if (deflate) { 544 Map<String,String> p = unpacker.properties(); 545 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE); 546 } 547 unpacker.unpack(gis, contentStream()); 548 } 549 550 } 551 552 private static void checkCompressor(SectionType type, 553 Compressor compressor) { 554 555 if ((SectionType.MODULE_INFO == type && 556 Compressor.NONE != compressor) 557 || (SectionType.CLASSES == type && 558 Compressor.PACK200_GZIP != compressor)) 559 throw new IllegalArgumentException(type 560 + " may not use compressor " 561 + compressor); 562 } 563 564 private static void checkSubsectionCount(SectionType type, 565 short subsections) { 566 if (!type.hasFiles() && subsections != 0) 567 throw new IllegalArgumentException(type 568 + " subsection count not 0: " 569 + subsections); 570 else if (type.hasFiles() && subsections == 0) 571 throw new IllegalArgumentException(type + " subsection count is 0"); 572 } 573 574 private static void copyStream(InputStream in, DataOutput out) 575 throws IOException 576 { 577 578 byte[] buffer = new byte[1024 * 8]; 579 for (int b_read = in.read(buffer); 580 -1 != b_read; 581 b_read = in.read(buffer)) 582 out.write(buffer, 0, b_read); 583 } 584 585 private static void copyStream(InputStream in, OutputStream out) 586 throws IOException 587 { 588 copyStream(in, (DataOutput) new DataOutputStream(out)); 589 } 590 591 private static void copyStream(InputStream in, DataOutput out, 592 int count) 593 throws IOException 594 { 595 byte[] buffer = new byte[1024 * 8]; 596 597 while(count > 0) { 598 int b_read = in.read(buffer, 0, Math.min(count, buffer.length)); 599 if (-1 == b_read) 600 return; 601 out.write(buffer, 0, b_read); 602 count-=b_read; 603 } 604 } 605 606 private static void copyStream(InputStream in, OutputStream out, 607 int count) 608 throws IOException 609 { 610 copyStream(in, (DataOutput) new DataOutputStream(out), count); 611 } 612 613 private static void ensureNonAbsolute(File path) throws IOException { 614 if (path.isAbsolute()) 615 throw new IOException("Abolute path instead of relative: " + path); 616 } 617 618 private static void ensureNonNegativity(long size, String parameter) { 619 if (size < 0) 620 throw new IllegalArgumentException(parameter + "<0: " + size); 621 } 622 623 private static void ensureNonNull(Object reference, String parameter) { 624 if (null == reference) 625 throw new IllegalArgumentException(parameter + " == null"); 626 } 627 628 private static void ensureMatch(int found, int expected, String field) 629 throws IOException 630 { 631 if (found != expected) 632 throw new IOException(field + " expected : " 633 + Integer.toHexString(expected) + " found: " 634 + Integer.toHexString(found)); 635 } 636 637 private static void ensureShortNativePath(File path, String name) 638 throws IOException 639 { 640 // TODO: check for native code file in a stricter way 641 if (path.canExecute() 642 && name.indexOf('/') != -1) 643 throw new IOException("Native code path too long: " + path); 644 } 645 646 private static void ensureValidFileSize(long size, File path) 647 throws IOException 648 { 649 if (size < 0 || size > Integer.MAX_VALUE) 650 throw new IOException("File " + path + " too large: " + size); 651 } 652 653 static MessageDigest getHashInstance(HashType hashtype) 654 throws IOException 655 { 656 try { 657 switch(hashtype) { 658 case SHA256: 659 return MessageDigest.getInstance("SHA-256"); 660 default: 661 throw new IOException("Unknown hash type: " + hashtype); 662 } 663 } 664 catch (NoSuchAlgorithmException ex) { 665 throw (IOException) (new IOException(hashtype + " not found")) 666 .initCause(ex); 667 } 668 } 669 670 private static short getMUTF8Length(String name) { 671 short size = 2; 672 673 for (int i = name.length()-1; i >= 0; i--) { 674 char ch = name.charAt(i); 675 676 if ('\u0001' <= ch && ch <= '\u007F') 677 size += 1; 678 else if ('\u0000' == ch 679 || '\u0080' <= ch && ch <= '\u07FF') 680 size += 2; 681 else 682 size += 3; 683 } 684 685 return size; 686 } 687 688 private static String hashHexString(byte[] hash) { 689 StringBuilder hex = new StringBuilder("0x"); 690 for (int i = 0; i < hash.length; i++) { 691 int val = (hash[i] & 0xFF); 692 if (val <= 16) 693 hex.append("0"); 694 hex.append(Integer.toHexString(val)); 695 } 696 return hex.toString(); 697 } 698 699 private static File resolveAndNormalize(File directory, String path) 700 throws IOException 701 { 702 File realpath = new File(directory, path); 703 if (directory != null && 704 ! realpath.toPath().startsWith(directory.toPath())) 705 throw new IOException("Bogus relative path: " + path); 706 707 return realpath; 708 } 709 710 private static short readHashLength(DataInputStream in) throws IOException { 711 final short hashLength = in.readShort(); 712 ensureNonNegativity(hashLength, "hashLength"); 713 714 return hashLength; 715 } 716 717 private static byte[] readHashBytes(DataInputStream in, short hashLength) 718 throws IOException 719 { 720 721 final byte[] hash = new byte[hashLength]; 722 in.readFully(hash); 723 724 return hash; 725 } 726 727 private static byte[] readHash(DataInputStream in) throws IOException { 728 return readHashBytes(in, readHashLength(in)); 729 } 730 731 private static byte[] readFileHash(DigestInputStream dis) 732 throws IOException 733 { 734 735 DataInputStream in = new DataInputStream(dis); 736 737 final short hashLength = readHashLength(in); 738 739 // Turn digest computation off before reading the file hash 740 dis.on(false); 741 byte[] hash = readHashBytes(in, hashLength); 742 // Turn digest computation on again afterwards. 743 dis.on(true); 744 745 return hash; 746 } 747 748 public final static class ModuleFileHeader { 749 public static final int LENGTH_WITHOUT_HASH = 30; 750 public static final int LENGTH = 751 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 752 753 // Fields are specified as unsigned. Treat signed values as bugs. 754 private final int magic; // MAGIC 755 private final FileConstants.Type type; // Type.MODULE_FILE 756 private final short major; // ModuleFile.MAJOR_VERSION 757 private final short minor; // ModuleFile.MINOR_VERSION 758 private final long csize; // Size of rest of file, compressed 759 private final long usize; // Space required for uncompressed contents 760 // (upper private final ound; need not be exact) 761 private final HashType hashType; // One of ModuleFile.HashType 762 // (applies final o all hashes in this file) 763 private final byte[] hash; // Hash of entire file (except this hash 764 // and the Signature section, if present) 765 766 public byte[] getHash() { 767 return hash.clone(); 768 } 769 770 private byte[] getHashNoClone() { 771 return hash; 772 } 773 774 public ModuleFileHeader(long csize, long usize, 775 HashType hashType, byte[] hash) { 776 ensureNonNegativity(csize, "csize"); 777 ensureNonNegativity(usize, "usize"); 778 779 magic = FileConstants.MAGIC; 780 type = FileConstants.Type.MODULE_FILE; 781 major = MAJOR_VERSION; 782 minor = MINOR_VERSION; 783 784 this.csize = csize; 785 this.usize = usize; 786 this.hashType = hashType; 787 this.hash = hash.clone(); 788 } 789 790 public void write(final DataOutput out) throws IOException { 791 out.writeInt(magic); 792 out.writeShort(type.value()); 793 out.writeShort(major); 794 out.writeShort(minor); 795 out.writeLong(csize); 796 out.writeLong(usize); 797 out.writeShort(hashType.value()); 798 writeHash(out, hash); 799 } 800 801 private static HashType lookupHashType(short value) { 802 for (HashType i : HashType.class.getEnumConstants()) { 803 if (i.value() == value) return i; 804 } 805 806 throw new IllegalArgumentException("No HashType exists with value " 807 + value); 808 } 809 810 public static ModuleFileHeader read(final DigestInputStream dis) 811 throws IOException 812 { 813 DataInputStream in = new DataInputStream(dis); 814 815 final int magic = in.readInt(); 816 ensureMatch(magic, FileConstants.MAGIC, 817 "FileConstants.MAGIC"); 818 819 final short type = in.readShort(); 820 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 821 "Type.MODULE_FILE"); 822 823 final short major = in.readShort(); 824 ensureMatch(major, MAJOR_VERSION, 825 "ModuleFile.MAJOR_VERSION"); 826 827 final short minor = in.readShort(); 828 ensureMatch(minor, MINOR_VERSION, 829 "ModuleFile.MINOR_VERSION"); 830 831 final long csize = in.readLong(); 832 final long usize = in.readLong(); 833 final short hashTypeValue = in.readShort(); 834 HashType hashType = lookupHashType(hashTypeValue); 835 final byte[] hash = readFileHash(dis); 836 837 return new ModuleFileHeader(csize, usize, hashType, hash); 838 } 839 840 public String toString() { 841 return "MODULE{csize=" + csize + 842 ", hash=" + hashHexString(hash) + "}"; 843 } 844 } 845 846 public final static class SectionHeader { 847 public static final int LENGTH_WITHOUT_HASH = 12; 848 public static final int LENGTH = 849 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 850 851 // Fields are specified as unsigned. Treat signed values as bugs. 852 private final SectionType type; 853 private final Compressor compressor; 854 private final int csize; // Size of section content, compressed 855 private final short subsections; // Number of following subsections 856 private final byte[] hash; // Hash of section content 857 858 public SectionHeader(SectionType type, 859 Compressor compressor, 860 int csize, short subsections, byte[] hash) { 861 ensureNonNull(type, "type"); 862 ensureNonNull(compressor, "compressor"); 863 ensureNonNegativity(csize, "csize"); 864 ensureNonNegativity(subsections, "subsections"); 865 ensureNonNull(hash, "hash"); 866 checkSubsectionCount(type, subsections); 867 checkCompressor(type, compressor); 868 869 this.type = type; 870 this.compressor = compressor; 871 this.csize = csize; 872 this.subsections = subsections; 873 this.hash = hash.clone(); 874 } 875 876 public void write(DataOutput out) throws IOException { 877 out.writeShort(type.value()); 878 out.writeShort(compressor.value()); 879 out.writeInt(csize); 880 out.writeShort(subsections); 881 writeHash(out, hash); 882 } 883 884 private static SectionType lookupSectionType(short value) { 885 for (SectionType i : SectionType.class.getEnumConstants()) { 886 if (i.value() == value) return i; 887 } 888 889 throw new 890 IllegalArgumentException("No SectionType exists with value " 891 + value); 892 } 893 894 private static Compressor lookupCompressor(short value) { 895 for (Compressor i : Compressor.class.getEnumConstants()) { 896 if (i.value() == value) return i; 897 } 898 899 throw new 900 IllegalArgumentException("No Compressor exists with value " 901 + value); 902 } 903 904 public static SectionHeader read(DataInputStream in) throws IOException { 905 short tvalue = in.readShort(); 906 final SectionType type = lookupSectionType(tvalue); 907 short cvalue = in.readShort(); 908 final Compressor compressor = lookupCompressor(cvalue); 909 final int csize = in.readInt(); 910 final short sections = in.readShort(); 911 final byte[] hash = readHash(in); 912 913 return new SectionHeader(type, compressor, csize, 914 sections, hash); 915 } 916 917 public SectionType getType() { 918 return type; 919 } 920 921 public Compressor getCompressor() { 922 return compressor; 923 } 924 925 public int getCSize() { 926 return csize; 927 } 928 929 public short getSubsections() { 930 return subsections; 931 } 932 933 public byte[] getHash() { 934 return hash.clone(); 935 } 936 937 private byte[] getHashNoClone() { 938 return hash; 939 } 940 941 public String toString() { 942 return "SectionHeader{type= " + type 943 + ", compressor=" + compressor 944 + ", csize=" + csize 945 + ", subsections=" + subsections 946 + ", hash=" + hashHexString(hash) + "}"; 947 } 948 } 949 950 public final static class SubSectionFileHeader { 951 private final int csize; // Size of file, compressed 952 private final String path; // Path name, in Java-modified UTF-8 953 954 public int getCSize() { 955 return csize; 956 } 957 958 public String getPath() { 959 return path; 960 } 961 962 public SubSectionFileHeader(int csize, String path) { 963 ensureNonNegativity(csize, "csize"); 964 ensureNonNull(path, "path"); 965 966 this.csize = csize; 967 this.path = path; 968 } 969 970 public void write(DataOutput out) throws IOException { 971 out.writeShort(SubSectionType.FILE.value()); 972 out.writeInt(csize); 973 out.writeUTF(path); 974 } 975 976 public static SubSectionFileHeader read(DataInputStream in) 977 throws IOException 978 { 979 final short type = in.readShort(); 980 ensureMatch(type, SubSectionType.FILE.value(), 981 "ModuleFile.SubSectionType.FILE"); 982 final int csize = in.readInt(); 983 final String path = in.readUTF(); 984 985 return new SubSectionFileHeader(csize, path); 986 } 987 } 988 989 private static void writeHash(DataOutput out, byte[] hash) 990 throws IOException 991 { 992 out.writeShort(hash.length); 993 out.write(hash); 994 } 995 } --- EOF ---