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 try { 272 if (contentStream != null) { 273 contentStream.close(); 274 contentStream = null; 275 } 276 } finally { 277 if (fileIn != null) { 278 fileIn.close(); 279 fileIn = null; 280 } 281 } 282 } finally { 283 if (oomFilesWriter != null) { 284 oomFilesWriter.close(); 285 oomFilesWriter = null; 286 } 287 } 288 } 289 290 291 public void readModule() throws IOException { 292 extract = false; 293 readStart(); 294 readRest(); 295 } 296 297 public void readModule(File dst) throws IOException { 298 readStart(); 299 readRest(dst, false); 300 } 301 302 private void readSignatureSection(DataInputStream stream, 303 DigestInputStream dis) 304 throws IOException 305 { 306 307 // Turn off digest computation before reading Signature Section 308 dis.on(false); 309 310 // Mark the starting position 311 stream.mark(MAX_SECTION_HEADER_LENGTH); 312 if (stream.read() != -1) { 313 stream.reset(); 314 SectionHeader header = SectionHeader.read(stream); 315 if (header != null && 316 header.getType() == SectionType.SIGNATURE) { 317 readSectionContent(header, stream); 318 } else { 319 // Revert back to the starting position 320 stream.reset(); 321 } 322 } 323 324 // Turn on digest computation again 325 dis.on(true); 326 } 327 328 private SectionType readSection(DataInputStream stream) 329 throws IOException 330 { 331 SectionHeader header = SectionHeader.read(stream); 332 readSectionContent(header, stream); 333 return header.getType(); 334 } 335 336 private void readSectionContent(SectionHeader header, 337 DataInputStream stream) 338 throws IOException 339 { 340 SectionType type = header.getType(); 341 Compressor compressor = header.getCompressor(); 342 int csize = header.getCSize(); 343 short subsections = 344 type.hasFiles() ? header.getSubsections() : 1; 345 346 CountingInputStream cs = new CountingInputStream(stream, csize); 347 sectionDigest.reset(); 348 DigestInputStream dis = new DigestInputStream(cs, sectionDigest); 349 DataInputStream in = new DataInputStream(dis); 350 351 for (int subsection = 0; subsection < subsections; subsection++) 352 readFile(in, compressor, type, csize); 353 354 byte[] headerHash = header.getHashNoClone(); 355 checkHashMatch(headerHash, sectionDigest.digest()); 356 if (header.getType() != SectionType.SIGNATURE) { 357 calculatedHashes.add(headerHash); 358 } 359 } 360 361 public void readFile(DataInputStream in, 362 Compressor compressor, 363 SectionType type, 364 int csize) 365 throws IOException 366 { 367 switch (compressor) { 368 case NONE: 369 if (type == SectionType.MODULE_INFO) { 370 moduleInfoBytes = readModuleInfo(in, csize); 371 372 } else if (type == SectionType.SIGNATURE) { 373 // Examine the Signature header 374 moduleSignatureType = (int)in.readShort(); 375 int length = in.readInt(); 376 moduleSignatureBytes = readModuleSignature(in, csize - 6); 377 if (length != moduleSignatureBytes.length) { 378 throw new IOException("Invalid Signature length"); 379 } 380 } else { 381 readUncompressedFile(in, type, csize); 382 } 383 break; 384 case GZIP: 385 readGZIPCompressedFile(in, type); 386 break; 387 case PACK200_GZIP: 388 readClasses( 389 new DataInputStream(new CountingInputStream(in, csize))); 390 break; 391 default: 392 throw new IOException("Unsupported Compressor for files: " + 393 compressor); 394 } 395 } 396 397 public void readClasses(DataInputStream in) throws IOException { 398 unpack200gzip(in); 399 } 400 401 private File currentPath = null; 402 // true if currentPath points a path outside the module directory 403 private boolean oomPath; // false 404 405 private OutputStream openOutputStream(SectionType type, 406 String path) 407 throws IOException 408 { 409 if (!extract) 410 return new NullOutputStream(); 411 currentPath = null; 412 assert type != SectionType.CLASSES; 413 if (type == SectionType.RESOURCES) 414 return Files.newOutputStream(contentStream(), path); 415 currentPath = computeRealPath(type, path); 416 File parent = currentPath.getParentFile(); 417 if (!parent.exists()) 418 Files.mkdirs(parent, currentPath.getName()); 419 return new BufferedOutputStream(new FileOutputStream(currentPath)); 420 } 421 422 private static class NullOutputStream extends OutputStream { 423 @Override 424 public void write(int b) throws IOException {} 425 @Override 426 public void write(byte[] b) throws IOException {} 427 @Override 428 public void write(byte[] b, int off, int len) throws IOException {} 429 } 430 431 public void readGZIPCompressedFile(DataInputStream in, 432 SectionType type) 433 throws IOException 434 { 435 SubSectionFileHeader header = SubSectionFileHeader.read(in); 436 int csize = header.getCSize(); 437 438 // Splice off the compressed file from input stream 439 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 440 copyStream(new CountingInputStream(in, csize), baos, csize); 441 442 byte[] compressedfile = baos.toByteArray(); 443 ByteArrayInputStream bain 444 = new ByteArrayInputStream(compressedfile); 445 try (GZIPInputStream gin = new GZIPInputStream(bain); 446 OutputStream out = openOutputStream(type, header.getPath())) { 447 copyStream(gin, out); 448 } 449 450 if (extract) { 451 markNativeCodeExecutable(type, currentPath); 452 if (oomPath) 453 trackOutOfModuleContent(currentPath); 454 } 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 markNativeCodeExecutable(type, currentPath); 474 if (oomPath) 475 trackOutOfModuleContent(currentPath); 476 } 477 } 478 479 public byte[] readModuleInfo(DataInputStream in, int csize) 480 throws IOException 481 { 482 CountingInputStream cin = new CountingInputStream(in, csize); 483 ByteArrayOutputStream out = new ByteArrayOutputStream(); 484 byte[] buf = new byte[8192]; 485 int n; 486 while ((n = cin.read(buf)) >= 0) 487 out.write(buf, 0, n); 488 return out.toByteArray(); 489 } 490 491 public byte[] readModuleSignature(DataInputStream in, int csize) 492 throws IOException 493 { 494 return readModuleInfo(in, csize); // signature has the same format 495 } 496 497 // Returns the path for the section type, if the library 498 // has one configured. 499 private File librarySectionPath(SectionType type) { 500 if (lib != null && type == SectionType.NATIVE_LIBS 501 && lib.natlibs() != null) 502 return lib.natlibs(); 503 if (lib != null && type == SectionType.NATIVE_CMDS 504 && lib.natcmds() != null) 505 return lib.natcmds(); 506 507 return null; 508 } 509 510 // Track files installed outside the module library. For later removal. 511 private PrintWriter oomFilesWriter; 512 513 private void trackOutOfModuleContent(File file) 514 throws IOException 515 { 516 if (file == null) 517 return; 518 519 // Lazy construction, not all modules will need this. 520 if (oomFilesWriter == null) { 521 oomFilesWriter = new PrintWriter(computeRealPath("oomfiles")); 522 } 523 oomFilesWriter.println(file); 524 oomFilesWriter.flush(); 525 } 526 527 void remove() throws IOException { 528 ModuleFile.Reader.remove(destination); 529 } 530 531 // Removes a module, given its module install directory 532 static void remove(File moduleDir) throws IOException { 533 // Firstly remove any files installed outside of the module dir 534 File oomfiles = new File(moduleDir, "oomfiles"); 535 if (oomfiles.exists()) { 536 try (FileInputStream fis = new FileInputStream(oomfiles); 537 BufferedReader in = new BufferedReader(new InputStreamReader(fis))) { 538 String filename; 539 while ((filename = in.readLine()) != null) 540 Files.delete(new File(filename)); 541 } 542 } 543 544 Files.deleteTree(moduleDir); 545 } 546 547 private File computeRealPath(String storedpath) throws IOException { 548 549 String convertedpath = storedpath.replace('/', File.separatorChar); 550 File path = new File(convertedpath); 551 552 // Absolute path names are not permitted. 553 ensureNonAbsolute(path); 554 path = resolveAndNormalize(destination, convertedpath); 555 // Create the parent directories if necessary 556 File parent = path.getParentFile(); 557 if (!parent.exists()) 558 Files.mkdirs(parent, path.getName()); 559 560 return path; 561 } 562 563 private File computeRealPath(SectionType type, 564 String storedpath) 565 throws IOException 566 { 567 File lsp = librarySectionPath(type); 568 if (lsp != null) { 569 // The library has a configured path for this section 570 File realpath = new File(lsp, storedpath); 571 572 if (realpath.exists()) { 573 // conflict, for now just fail 574 throw new IOException("File " + realpath + " already exists"); 575 } 576 577 // Create the parent directories if necessary 578 File parent = realpath.getParentFile(); 579 if (!parent.exists()) 580 Files.mkdirs(parent, realpath.getName()); 581 582 oomPath = true; 583 return realpath; 584 } 585 586 // reset since the path must be within the module directory 587 oomPath = false; 588 String dir = getSubdirOfSection(type); 589 return computeRealPath(dir + File.separatorChar + storedpath); 590 } 591 592 private static void markNativeCodeExecutable(SectionType type, 593 File file) 594 { 595 if (type == SectionType.NATIVE_CMDS 596 || (type == SectionType.NATIVE_LIBS 597 && System.getProperty("os.name").startsWith("Windows"))) 598 { 599 file.setExecutable(true); 600 } 601 } 602 603 private void unpack200gzip(DataInputStream in) throws IOException { 604 GZIPInputStream gis = new GZIPInputStream(in) { 605 public void close() throws IOException {} 606 }; 607 Pack200.Unpacker unpacker = Pack200.newUnpacker(); 608 if (deflate) { 609 Map<String,String> p = unpacker.properties(); 610 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE); 611 } 612 unpacker.unpack(gis, contentStream()); 613 } 614 615 } 616 617 private static void checkCompressor(SectionType type, 618 Compressor compressor) { 619 620 if ((SectionType.MODULE_INFO == type && 621 Compressor.NONE != compressor) 622 || (SectionType.CLASSES == type && 623 Compressor.PACK200_GZIP != compressor)) 624 throw new IllegalArgumentException(type 625 + " may not use compressor " 626 + compressor); 627 } 628 629 private static void checkSubsectionCount(SectionType type, 630 short subsections) { 631 if (!type.hasFiles() && subsections != 0) 632 throw new IllegalArgumentException(type 633 + " subsection count not 0: " 634 + subsections); 635 else if (type.hasFiles() && subsections == 0) 636 throw new IllegalArgumentException(type + " subsection count is 0"); 637 } 638 639 private static void copyStream(InputStream in, DataOutput out) 640 throws IOException 641 { 642 643 byte[] buffer = new byte[1024 * 8]; 644 for (int b_read = in.read(buffer); 645 -1 != b_read; 646 b_read = in.read(buffer)) 647 out.write(buffer, 0, b_read); 648 } 649 650 private static void copyStream(InputStream in, OutputStream out) 651 throws IOException 652 { 653 copyStream(in, (DataOutput) new DataOutputStream(out)); 654 } 655 656 private static void copyStream(InputStream in, DataOutput out, 657 int count) 658 throws IOException 659 { 660 byte[] buffer = new byte[1024 * 8]; 661 662 while(count > 0) { 663 int b_read = in.read(buffer, 0, Math.min(count, buffer.length)); 664 if (-1 == b_read) 665 return; 666 out.write(buffer, 0, b_read); 667 count-=b_read; 668 } 669 } 670 671 private static void copyStream(InputStream in, OutputStream out, 672 int count) 673 throws IOException 674 { 675 copyStream(in, (DataOutput) new DataOutputStream(out), count); 676 } 677 678 private static void ensureNonAbsolute(File path) throws IOException { 679 if (path.isAbsolute()) 680 throw new IOException("Abolute path instead of relative: " + path); 681 } 682 683 private static void ensureNonNegativity(long size, String parameter) { 684 if (size < 0) 685 throw new IllegalArgumentException(parameter + "<0: " + size); 686 } 687 688 private static void ensureNonNull(Object reference, String parameter) { 689 if (null == reference) 690 throw new IllegalArgumentException(parameter + " == null"); 691 } 692 693 private static void ensureMatch(int found, int expected, String field) 694 throws IOException 695 { 696 if (found != expected) 697 throw new IOException(field + " expected : " 698 + Integer.toHexString(expected) + " found: " 699 + Integer.toHexString(found)); 700 } 701 702 private static void ensureShortNativePath(File path, String name) 703 throws IOException 704 { 705 // TODO: check for native code file in a stricter way 706 if (path.canExecute() 707 && name.indexOf('/') != -1) 708 throw new IOException("Native code path too long: " + path); 709 } 710 711 private static void ensureValidFileSize(long size, File path) 712 throws IOException 713 { 714 if (size < 0 || size > Integer.MAX_VALUE) 715 throw new IOException("File " + path + " too large: " + size); 716 } 717 718 static MessageDigest getHashInstance(HashType hashtype) 719 throws IOException 720 { 721 try { 722 switch(hashtype) { 723 case SHA256: 724 return MessageDigest.getInstance("SHA-256"); 725 default: 726 throw new IOException("Unknown hash type: " + hashtype); 727 } 728 } 729 catch (NoSuchAlgorithmException ex) { 730 throw (IOException) (new IOException(hashtype + " not found")) 731 .initCause(ex); 732 } 733 } 734 735 private static short getMUTF8Length(String name) { 736 short size = 2; 737 738 for (int i = name.length()-1; i >= 0; i--) { 739 char ch = name.charAt(i); 740 741 if ('\u0001' <= ch && ch <= '\u007F') 742 size += 1; 743 else if ('\u0000' == ch 744 || '\u0080' <= ch && ch <= '\u07FF') 745 size += 2; 746 else 747 size += 3; 748 } 749 750 return size; 751 } 752 753 private static String hashHexString(byte[] hash) { 754 StringBuilder hex = new StringBuilder("0x"); 755 for (int i = 0; i < hash.length; i++) { 756 int val = (hash[i] & 0xFF); 757 if (val <= 16) 758 hex.append("0"); 759 hex.append(Integer.toHexString(val)); 760 } 761 return hex.toString(); 762 } 763 764 private static File resolveAndNormalize(File directory, String path) 765 throws IOException 766 { 767 File realpath = new File(directory, path); 768 if (directory != null && 769 ! realpath.toPath().startsWith(directory.toPath())) 770 throw new IOException("Bogus relative path: " + path); 771 772 return realpath; 773 } 774 775 private static short readHashLength(DataInputStream in) throws IOException { 776 final short hashLength = in.readShort(); 777 ensureNonNegativity(hashLength, "hashLength"); 778 779 return hashLength; 780 } 781 782 private static byte[] readHashBytes(DataInputStream in, short hashLength) 783 throws IOException 784 { 785 786 final byte[] hash = new byte[hashLength]; 787 in.readFully(hash); 788 789 return hash; 790 } 791 792 private static byte[] readHash(DataInputStream in) throws IOException { 793 return readHashBytes(in, readHashLength(in)); 794 } 795 796 private static byte[] readFileHash(DigestInputStream dis) 797 throws IOException 798 { 799 800 DataInputStream in = new DataInputStream(dis); 801 802 final short hashLength = readHashLength(in); 803 804 // Turn digest computation off before reading the file hash 805 dis.on(false); 806 byte[] hash = readHashBytes(in, hashLength); 807 // Turn digest computation on again afterwards. 808 dis.on(true); 809 810 return hash; 811 } 812 813 public final static class ModuleFileHeader { 814 public static final int LENGTH_WITHOUT_HASH = 30; 815 public static final int LENGTH = 816 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 817 818 // Fields are specified as unsigned. Treat signed values as bugs. 819 private final int magic; // MAGIC 820 private final FileConstants.Type type; // Type.MODULE_FILE 821 private final short major; // ModuleFile.MAJOR_VERSION 822 private final short minor; // ModuleFile.MINOR_VERSION 823 private final long csize; // Size of rest of file, compressed 824 private final long usize; // Space required for uncompressed contents 825 // (upper private final ound; need not be exact) 826 private final HashType hashType; // One of ModuleFile.HashType 827 // (applies final o all hashes in this file) 828 private final byte[] hash; // Hash of entire file (except this hash 829 // and the Signature section, if present) 830 831 public byte[] getHash() { 832 return hash.clone(); 833 } 834 835 private byte[] getHashNoClone() { 836 return hash; 837 } 838 839 public ModuleFileHeader(long csize, long usize, 840 HashType hashType, byte[] hash) { 841 ensureNonNegativity(csize, "csize"); 842 ensureNonNegativity(usize, "usize"); 843 844 magic = FileConstants.MAGIC; 845 type = FileConstants.Type.MODULE_FILE; 846 major = MAJOR_VERSION; 847 minor = MINOR_VERSION; 848 849 this.csize = csize; 850 this.usize = usize; 851 this.hashType = hashType; 852 this.hash = hash.clone(); 853 } 854 855 public void write(final DataOutput out) throws IOException { 856 out.writeInt(magic); 857 out.writeShort(type.value()); 858 out.writeShort(major); 859 out.writeShort(minor); 860 out.writeLong(csize); 861 out.writeLong(usize); 862 out.writeShort(hashType.value()); 863 writeHash(out, hash); 864 } 865 866 private static HashType lookupHashType(short value) { 867 for (HashType i : HashType.class.getEnumConstants()) { 868 if (i.value() == value) return i; 869 } 870 871 throw new IllegalArgumentException("No HashType exists with value " 872 + value); 873 } 874 875 public static ModuleFileHeader read(final DigestInputStream dis) 876 throws IOException 877 { 878 DataInputStream in = new DataInputStream(dis); 879 880 final int magic = in.readInt(); 881 ensureMatch(magic, FileConstants.MAGIC, 882 "FileConstants.MAGIC"); 883 884 final short type = in.readShort(); 885 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 886 "Type.MODULE_FILE"); 887 888 final short major = in.readShort(); 889 ensureMatch(major, MAJOR_VERSION, 890 "ModuleFile.MAJOR_VERSION"); 891 892 final short minor = in.readShort(); 893 ensureMatch(minor, MINOR_VERSION, 894 "ModuleFile.MINOR_VERSION"); 895 896 final long csize = in.readLong(); 897 final long usize = in.readLong(); 898 final short hashTypeValue = in.readShort(); 899 HashType hashType = lookupHashType(hashTypeValue); 900 final byte[] hash = readFileHash(dis); 901 902 return new ModuleFileHeader(csize, usize, hashType, hash); 903 } 904 905 public String toString() { 906 return "MODULE{csize=" + csize + 907 ", hash=" + hashHexString(hash) + "}"; 908 } 909 } 910 911 public final static class SectionHeader { 912 public static final int LENGTH_WITHOUT_HASH = 12; 913 public static final int LENGTH = 914 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 915 916 // Fields are specified as unsigned. Treat signed values as bugs. 917 private final SectionType type; 918 private final Compressor compressor; 919 private final int csize; // Size of section content, compressed 920 private final short subsections; // Number of following subsections 921 private final byte[] hash; // Hash of section content 922 923 public SectionHeader(SectionType type, 924 Compressor compressor, 925 int csize, short subsections, byte[] hash) { 926 ensureNonNull(type, "type"); 927 ensureNonNull(compressor, "compressor"); 928 ensureNonNegativity(csize, "csize"); 929 ensureNonNegativity(subsections, "subsections"); 930 ensureNonNull(hash, "hash"); 931 checkSubsectionCount(type, subsections); 932 checkCompressor(type, compressor); 933 934 this.type = type; 935 this.compressor = compressor; 936 this.csize = csize; 937 this.subsections = subsections; 938 this.hash = hash.clone(); 939 } 940 941 public void write(DataOutput out) throws IOException { 942 out.writeShort(type.value()); 943 out.writeShort(compressor.value()); 944 out.writeInt(csize); 945 out.writeShort(subsections); 946 writeHash(out, hash); 947 } 948 949 private static SectionType lookupSectionType(short value) { 950 for (SectionType i : SectionType.class.getEnumConstants()) { 951 if (i.value() == value) return i; 952 } 953 954 throw new 955 IllegalArgumentException("No SectionType exists with value " 956 + value); 957 } 958 959 private static Compressor lookupCompressor(short value) { 960 for (Compressor i : Compressor.class.getEnumConstants()) { 961 if (i.value() == value) return i; 962 } 963 964 throw new 965 IllegalArgumentException("No Compressor exists with value " 966 + value); 967 } 968 969 public static SectionHeader read(DataInputStream in) throws IOException { 970 short tvalue = in.readShort(); 971 final SectionType type = lookupSectionType(tvalue); 972 short cvalue = in.readShort(); 973 final Compressor compressor = lookupCompressor(cvalue); 974 final int csize = in.readInt(); 975 final short sections = in.readShort(); 976 final byte[] hash = readHash(in); 977 978 return new SectionHeader(type, compressor, csize, 979 sections, hash); 980 } 981 982 public SectionType getType() { 983 return type; 984 } 985 986 public Compressor getCompressor() { 987 return compressor; 988 } 989 990 public int getCSize() { 991 return csize; 992 } 993 994 public short getSubsections() { 995 return subsections; 996 } 997 998 public byte[] getHash() { 999 return hash.clone(); 1000 } 1001 1002 private byte[] getHashNoClone() { 1003 return hash; 1004 } 1005 1006 public String toString() { 1007 return "SectionHeader{type= " + type 1008 + ", compressor=" + compressor 1009 + ", csize=" + csize 1010 + ", subsections=" + subsections 1011 + ", hash=" + hashHexString(hash) + "}"; 1012 } 1013 } 1014 1015 public final static class SubSectionFileHeader { 1016 private final int csize; // Size of file, compressed 1017 private final String path; // Path name, in Java-modified UTF-8 1018 1019 public int getCSize() { 1020 return csize; 1021 } 1022 1023 public String getPath() { 1024 return path; 1025 } 1026 1027 public SubSectionFileHeader(int csize, String path) { 1028 ensureNonNegativity(csize, "csize"); 1029 ensureNonNull(path, "path"); 1030 1031 this.csize = csize; 1032 this.path = path; 1033 } 1034 1035 public void write(DataOutput out) throws IOException { 1036 out.writeShort(SubSectionType.FILE.value()); 1037 out.writeInt(csize); 1038 out.writeUTF(path); 1039 } 1040 1041 public static SubSectionFileHeader read(DataInputStream in) 1042 throws IOException 1043 { 1044 final short type = in.readShort(); 1045 ensureMatch(type, SubSectionType.FILE.value(), 1046 "ModuleFile.SubSectionType.FILE"); 1047 final int csize = in.readInt(); 1048 final String path = in.readUTF(); 1049 1050 return new SubSectionFileHeader(csize, path); 1051 } 1052 } 1053 1054 private static void writeHash(DataOutput out, byte[] hash) 1055 throws IOException 1056 { 1057 out.writeShort(hash.length); 1058 out.write(hash); 1059 } 1060 } --- EOF ---