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