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