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 private DataInputStream stream; 62 private File destination; 63 private boolean deflate; 64 private HashType hashtype; 65 private File natlibs; 66 private File natcmds; 67 private File configs; 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 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 Integer 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.destination = dst.getCanonicalFile(); 198 this.deflate = deflate; 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 Integer 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 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 void remove() throws IOException { 514 ModuleFile.Reader.remove(destination); 515 } 516 517 // Removes a module, given its module install directory 518 static void remove(File moduleDir) throws IOException { 519 // Firstly remove any files installed outside of the module dir 520 File files = new File(moduleDir, "files"); 521 if (files.exists()) { 522 try (FileInputStream fis = new FileInputStream(files); 523 InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); 524 BufferedReader in = new BufferedReader(isr)) { 525 String filename; 526 while ((filename = in.readLine()) != null) 527 Files.delete(new File(moduleDir, 528 Files.platformSeparator(filename))); 529 } 530 } 531 532 Files.deleteTree(moduleDir); 533 } 534 535 // Returns the absolute path of the given section type. 536 private File getDirOfSection(SectionType type) { 537 if (type == SectionType.NATIVE_LIBS) 538 return natlibs; 539 else if (type == SectionType.NATIVE_CMDS) 540 return natcmds; 541 else if (type == SectionType.CONFIG) 542 return configs; 543 544 // resolve sub dir section paths against the modules directory 545 return new File(destination, ModuleFile.getSubdirOfSection(type)); 546 } 547 548 private File computeRealPath(String path) throws IOException { 549 return resolveAndNormalize(destination, path); 550 } 551 552 private File computeRealPath(SectionType type, String storedpath) 553 throws IOException 554 { 555 File sectionPath = getDirOfSection(type); 556 File realpath = new File(sectionPath, 557 Files.ensureNonAbsolute(Files.platformSeparator(storedpath))); 558 559 validatePath(sectionPath, realpath); 560 561 // Create the parent directories if necessary 562 File parent = realpath.getParentFile(); 563 if (!parent.exists()) 564 Files.mkdirs(parent, realpath.getName()); 565 566 return realpath; 567 } 568 569 private static void markNativeCodeExecutable(SectionType type, 570 File file) 571 { 572 if (type == SectionType.NATIVE_CMDS 573 || (type == SectionType.NATIVE_LIBS 574 && System.getProperty("os.name").startsWith("Windows"))) 575 { 576 file.setExecutable(true); 577 } 578 } 579 580 private void postExtract(SectionType type, File path) 581 throws IOException 582 { 583 markNativeCodeExecutable(type, path); 584 trackFiles(type, path); 585 } 586 587 private void unpack200gzip(DataInputStream in) throws IOException { 588 GZIPInputStream gis = new GZIPInputStream(in) { 589 public void close() throws IOException {} 590 }; 591 Pack200.Unpacker unpacker = Pack200.newUnpacker(); 592 if (deflate) { 593 Map<String,String> p = unpacker.properties(); 594 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE); 595 } 596 unpacker.unpack(gis, contentStream()); 597 } 598 599 } 600 601 private static void checkCompressor(SectionType type, 602 Compressor compressor) { 603 604 if ((SectionType.MODULE_INFO == type && 605 Compressor.NONE != compressor) 606 || (SectionType.CLASSES == type && 607 Compressor.PACK200_GZIP != compressor)) 608 throw new IllegalArgumentException(type 609 + " may not use compressor " 610 + compressor); 611 } 612 613 private static void checkSubsectionCount(SectionType type, 614 short subsections) { 615 if (!type.hasFiles() && subsections != 0) 616 throw new IllegalArgumentException(type 617 + " subsection count not 0: " 618 + subsections); 619 else if (type.hasFiles() && subsections == 0) 620 throw new IllegalArgumentException(type + " subsection count is 0"); 621 } 622 623 private static void copyStream(InputStream in, DataOutput out) 624 throws IOException 625 { 626 627 byte[] buffer = new byte[1024 * 8]; 628 for (int b_read = in.read(buffer); 629 -1 != b_read; 630 b_read = in.read(buffer)) 631 out.write(buffer, 0, b_read); 632 } 633 634 private static void copyStream(InputStream in, OutputStream out) 635 throws IOException 636 { 637 copyStream(in, (DataOutput) new DataOutputStream(out)); 638 } 639 640 private static void copyStream(InputStream in, DataOutput out, 641 int count) 642 throws IOException 643 { 644 byte[] buffer = new byte[1024 * 8]; 645 646 while(count > 0) { 647 int b_read = in.read(buffer, 0, Math.min(count, buffer.length)); 648 if (-1 == b_read) 649 return; 650 out.write(buffer, 0, b_read); 651 count-=b_read; 652 } 653 } 654 655 private static void copyStream(InputStream in, OutputStream out, 656 int count) 657 throws IOException 658 { 659 copyStream(in, (DataOutput) new DataOutputStream(out), count); 660 } 661 662 private static void ensureNonNegativity(long size, String parameter) { 663 if (size < 0) 664 throw new IllegalArgumentException(parameter + "<0: " + size); 665 } 666 667 private static void ensureNonNull(Object reference, String parameter) { 668 if (null == reference) 669 throw new IllegalArgumentException(parameter + " == null"); 670 } 671 672 private static void ensureMatch(int found, int expected, String field) 673 throws IOException 674 { 675 if (found != expected) 676 throw new IOException(field + " expected : " 677 + Integer.toHexString(expected) + " found: " 678 + Integer.toHexString(found)); 679 } 680 681 private static void ensureShortNativePath(File path, String name) 682 throws IOException 683 { 684 // TODO: check for native code file in a stricter way 685 if (path.canExecute() 686 && name.indexOf('/') != -1) 687 throw new IOException("Native code path too long: " + path); 688 } 689 690 private static void ensureValidFileSize(long size, File path) 691 throws IOException 692 { 693 if (size < 0 || size > Integer.MAX_VALUE) 694 throw new IOException("File " + path + " too large: " + size); 695 } 696 697 static MessageDigest getHashInstance(HashType hashtype) 698 throws IOException 699 { 700 try { 701 switch(hashtype) { 702 case SHA256: 703 return MessageDigest.getInstance("SHA-256"); 704 default: 705 throw new IOException("Unknown hash type: " + hashtype); 706 } 707 } 708 catch (NoSuchAlgorithmException ex) { 709 throw (IOException) (new IOException(hashtype + " not found")) 710 .initCause(ex); 711 } 712 } 713 714 private static short getMUTF8Length(String name) { 715 short size = 2; 716 717 for (int i = name.length()-1; i >= 0; i--) { 718 char ch = name.charAt(i); 719 720 if ('\u0001' <= ch && ch <= '\u007F') 721 size += 1; 722 else if ('\u0000' == ch 723 || '\u0080' <= ch && ch <= '\u07FF') 724 size += 2; 725 else 726 size += 3; 727 } 728 729 return size; 730 } 731 732 private static String hashHexString(byte[] hash) { 733 StringBuilder hex = new StringBuilder("0x"); 734 for (int i = 0; i < hash.length; i++) { 735 int val = (hash[i] & 0xFF); 736 if (val <= 16) 737 hex.append("0"); 738 hex.append(Integer.toHexString(val)); 739 } 740 return hex.toString(); 741 } 742 743 private static File resolveAndNormalize(File directory, String path) 744 throws IOException 745 { 746 File realpath = new File(directory, path); 747 if (directory != null && 748 ! realpath.toPath().startsWith(directory.toPath())) 749 throw new IOException("Bogus relative path: " + path); 750 751 return realpath; 752 } 753 754 755 private static String relativize(File directory, File path) throws IOException { 756 return (directory.toPath().relativize(path.toPath().toRealPath())).toString(); 757 } 758 759 private static void validatePath(File parent, File child) 760 throws IOException 761 { 762 if (!child.toPath().startsWith(parent.toPath()) ) 763 throw new IOException("Bogus relative path: " + child); 764 if (child.exists()) { 765 // conflict, for now just fail 766 throw new IOException("File " + child + " already exists"); 767 } 768 } 769 770 private static short readHashLength(DataInputStream in) throws IOException { 771 final short hashLength = in.readShort(); 772 ensureNonNegativity(hashLength, "hashLength"); 773 774 return hashLength; 775 } 776 777 private static byte[] readHashBytes(DataInputStream in, short hashLength) 778 throws IOException 779 { 780 781 final byte[] hash = new byte[hashLength]; 782 in.readFully(hash); 783 784 return hash; 785 } 786 787 private static byte[] readHash(DataInputStream in) throws IOException { 788 return readHashBytes(in, readHashLength(in)); 789 } 790 791 private static byte[] readFileHash(DigestInputStream dis) 792 throws IOException 793 { 794 795 DataInputStream in = new DataInputStream(dis); 796 797 final short hashLength = readHashLength(in); 798 799 // Turn digest computation off before reading the file hash 800 dis.on(false); 801 byte[] hash = readHashBytes(in, hashLength); 802 // Turn digest computation on again afterwards. 803 dis.on(true); 804 805 return hash; 806 } 807 808 public final static class ModuleFileHeader { 809 public static final int LENGTH_WITHOUT_HASH = 30; 810 public static final int LENGTH = 811 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 812 813 // Fields are specified as unsigned. Treat signed values as bugs. 814 private final int magic; // MAGIC 815 private final FileConstants.Type type; // Type.MODULE_FILE 816 private final short major; // ModuleFile.MAJOR_VERSION 817 private final short minor; // ModuleFile.MINOR_VERSION 818 private final long csize; // Size of rest of file, compressed 819 private final long usize; // Space required for uncompressed contents 820 // (upper private final ound; need not be exact) 821 private final HashType hashType; // One of ModuleFile.HashType 822 // (applies final o all hashes in this file) 823 private final byte[] hash; // Hash of entire file (except this hash 824 // and the Signature section, if present) 825 826 public byte[] getHash() { 827 return hash.clone(); 828 } 829 830 private byte[] getHashNoClone() { 831 return hash; 832 } 833 834 public ModuleFileHeader(long csize, long usize, 835 HashType hashType, byte[] hash) { 836 ensureNonNegativity(csize, "csize"); 837 ensureNonNegativity(usize, "usize"); 838 839 magic = FileConstants.MAGIC; 840 type = FileConstants.Type.MODULE_FILE; 841 major = MAJOR_VERSION; 842 minor = MINOR_VERSION; 843 844 this.csize = csize; 845 this.usize = usize; 846 this.hashType = hashType; 847 this.hash = hash.clone(); 848 } 849 850 public void write(final DataOutput out) throws IOException { 851 out.writeInt(magic); 852 out.writeShort(type.value()); 853 out.writeShort(major); 854 out.writeShort(minor); 855 out.writeLong(csize); 856 out.writeLong(usize); 857 out.writeShort(hashType.value()); 858 writeHash(out, hash); 859 } 860 861 private static HashType lookupHashType(short value) { 862 for (HashType i : HashType.class.getEnumConstants()) { 863 if (i.value() == value) return i; 864 } 865 866 throw new IllegalArgumentException("No HashType exists with value " 867 + value); 868 } 869 870 public static ModuleFileHeader read(final DigestInputStream dis) 871 throws IOException 872 { 873 DataInputStream in = new DataInputStream(dis); 874 875 final int magic = in.readInt(); 876 ensureMatch(magic, FileConstants.MAGIC, 877 "FileConstants.MAGIC"); 878 879 final short type = in.readShort(); 880 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 881 "Type.MODULE_FILE"); 882 883 final short major = in.readShort(); 884 ensureMatch(major, MAJOR_VERSION, 885 "ModuleFile.MAJOR_VERSION"); 886 887 final short minor = in.readShort(); 888 ensureMatch(minor, MINOR_VERSION, 889 "ModuleFile.MINOR_VERSION"); 890 891 final long csize = in.readLong(); 892 final long usize = in.readLong(); 893 final short hashTypeValue = in.readShort(); 894 HashType hashType = lookupHashType(hashTypeValue); 895 final byte[] hash = readFileHash(dis); 896 897 return new ModuleFileHeader(csize, usize, hashType, hash); 898 } 899 900 public String toString() { 901 return "MODULE{csize=" + csize + 902 ", hash=" + hashHexString(hash) + "}"; 903 } 904 } 905 906 public final static class SectionHeader { 907 public static final int LENGTH_WITHOUT_HASH = 12; 908 public static final int LENGTH = 909 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 910 911 // Fields are specified as unsigned. Treat signed values as bugs. 912 private final SectionType type; 913 private final Compressor compressor; 914 private final int csize; // Size of section content, compressed 915 private final short subsections; // Number of following subsections 916 private final byte[] hash; // Hash of section content 917 918 public SectionHeader(SectionType type, 919 Compressor compressor, 920 int csize, short subsections, byte[] hash) { 921 ensureNonNull(type, "type"); 922 ensureNonNull(compressor, "compressor"); 923 ensureNonNegativity(csize, "csize"); 924 ensureNonNegativity(subsections, "subsections"); 925 ensureNonNull(hash, "hash"); 926 checkSubsectionCount(type, subsections); 927 checkCompressor(type, compressor); 928 929 this.type = type; 930 this.compressor = compressor; 931 this.csize = csize; 932 this.subsections = subsections; 933 this.hash = hash.clone(); 934 } 935 936 public void write(DataOutput out) throws IOException { 937 out.writeShort(type.value()); 938 out.writeShort(compressor.value()); 939 out.writeInt(csize); 940 out.writeShort(subsections); 941 writeHash(out, hash); 942 } 943 944 private static SectionType lookupSectionType(short value) { 945 for (SectionType i : SectionType.class.getEnumConstants()) { 946 if (i.value() == value) return i; 947 } 948 949 throw new 950 IllegalArgumentException("No SectionType exists with value " 951 + value); 952 } 953 954 private static Compressor lookupCompressor(short value) { 955 for (Compressor i : Compressor.class.getEnumConstants()) { 956 if (i.value() == value) return i; 957 } 958 959 throw new 960 IllegalArgumentException("No Compressor exists with value " 961 + value); 962 } 963 964 public static SectionHeader read(DataInputStream in) throws IOException { 965 short tvalue = in.readShort(); 966 final SectionType type = lookupSectionType(tvalue); 967 short cvalue = in.readShort(); 968 final Compressor compressor = lookupCompressor(cvalue); 969 final int csize = in.readInt(); 970 final short sections = in.readShort(); 971 final byte[] hash = readHash(in); 972 973 return new SectionHeader(type, compressor, csize, 974 sections, hash); 975 } 976 977 public SectionType getType() { 978 return type; 979 } 980 981 public Compressor getCompressor() { 982 return compressor; 983 } 984 985 public int getCSize() { 986 return csize; 987 } 988 989 public short getSubsections() { 990 return subsections; 991 } 992 993 public byte[] getHash() { 994 return hash.clone(); 995 } 996 997 private byte[] getHashNoClone() { 998 return hash; 999 } 1000 1001 public String toString() { 1002 return "SectionHeader{type= " + type 1003 + ", compressor=" + compressor 1004 + ", csize=" + csize 1005 + ", subsections=" + subsections 1006 + ", hash=" + hashHexString(hash) + "}"; 1007 } 1008 } 1009 1010 public final static class SubSectionFileHeader { 1011 private final int csize; // Size of file, compressed 1012 private final String path; // Path name, in Java-modified UTF-8 1013 1014 public int getCSize() { 1015 return csize; 1016 } 1017 1018 public String getPath() { 1019 return path; 1020 } 1021 1022 public SubSectionFileHeader(int csize, String path) { 1023 ensureNonNegativity(csize, "csize"); 1024 ensureNonNull(path, "path"); 1025 1026 this.csize = csize; 1027 this.path = path; 1028 } 1029 1030 public void write(DataOutput out) throws IOException { 1031 out.writeShort(SubSectionType.FILE.value()); 1032 out.writeInt(csize); 1033 out.writeUTF(path); 1034 } 1035 1036 public static SubSectionFileHeader read(DataInputStream in) 1037 throws IOException 1038 { 1039 final short type = in.readShort(); 1040 ensureMatch(type, SubSectionType.FILE.value(), 1041 "ModuleFile.SubSectionType.FILE"); 1042 final int csize = in.readInt(); 1043 final String path = in.readUTF(); 1044 1045 return new SubSectionFileHeader(csize, path); 1046 } 1047 } 1048 1049 private static void writeHash(DataOutput out, byte[] hash) 1050 throws IOException 1051 { 1052 out.writeShort(hash.length); 1053 out.write(hash); 1054 } 1055 }