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