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.Map.Entry; 33 import java.util.jar.*; 34 import org.openjdk.jigsaw.ModuleFileParser.Event; 35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*; 36 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*; 37 import static org.openjdk.jigsaw.ModuleFileParser.Event.*; 38 39 40 /** 41 * <p> A known <a 42 * href="http://cr.openjdk.java.net/~mr/jigsaw/notes/module-file-format/"> 43 * module file</a> </p> 44 */ 45 46 public final class ModuleFile { 47 48 /** 49 * Return the subdir of a section in an extracted module file. 50 */ 51 public static String getSubdirOfSection(SectionType type) { 52 switch (type) { 53 case MODULE_INFO: 54 case SIGNATURE: 55 return "."; 56 case CLASSES: 57 case RESOURCES: 58 return "classes"; 59 case NATIVE_LIBS: 60 return "lib"; 61 case NATIVE_CMDS: 62 return "bin"; 63 case CONFIG: 64 return "etc"; 65 default: 66 throw new AssertionError(type); 67 } 68 } 69 70 /** 71 * Returns a ModuleFileParser instance. 72 * 73 * @param stream 74 * module file stream 75 * 76 * @return a module file parser 77 * 78 * @throws ModuleFileParserException 79 * If there is an error processing the underlying module file 80 */ 81 public static ModuleFileParser newParser(InputStream stream) { 82 return new ModuleFileParserImpl(stream); 83 } 84 85 /** 86 * Returns a ValidatingModuleFileParser instance. 87 * 88 * @param stream 89 * module file stream 90 * 91 * @return a validating module file parser 92 * 93 * @throws ModuleFileParserException 94 * If there is an error processing the underlying module file 95 */ 96 public static ValidatingModuleFileParser newValidatingParser(InputStream stream) { 97 return new ValidatingModuleFileParserImpl(stream); 98 } 99 100 /** 101 * <p> A module-file reader </p> 102 */ 103 public final static class Reader implements Closeable { 104 private final ValidatingModuleFileParser parser; 105 private final ModuleFileHeader fileHeader; 106 private final byte[] moduleInfoBytes; 107 private final SignatureType moduleSignatureType; 108 private final byte[] moduleSignatureBytes ; 109 110 private File destination; 111 private boolean deflate; 112 private File natlibs; 113 private File natcmds; 114 private File configs; 115 116 public Reader(InputStream stream) throws IOException { 117 parser = ModuleFile.newValidatingParser(stream); 118 fileHeader = parser.fileHeader(); 119 // Read the MODULE_INFO and the Signature section (if present), 120 // but does not write any files. 121 parser.next(); 122 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 123 copyStream(parser.getRawStream(), baos); 124 moduleInfoBytes = baos.toByteArray(); 125 assert moduleInfoBytes != null; 126 127 if (parser.next() != END_SECTION) 128 throw new ModuleFileParserException( 129 "Expected END_SECTION of module-info"); 130 131 if (parser.hasNext()) { 132 Event event = parser.next(); 133 if (event != END_FILE) { // more sections 134 SectionHeader header = parser.getSectionHeader(); 135 if (header.type == SIGNATURE) { 136 SignatureSection sh = SignatureSection.read( 137 new DataInputStream(parser.getRawStream())); 138 moduleSignatureType = SignatureType.valueOf(sh.getSignatureType()); 139 moduleSignatureBytes = sh.getSignature(); 140 if (parser.next() != END_SECTION) 141 throw new ModuleFileParserException( 142 "Expected END_SECTION of signature"); 143 if (parser.hasNext()) 144 parser.next(); // position parser at next event 145 return; 146 } 147 } 148 } 149 // no signature section, or possibly other sections at all. 150 moduleSignatureBytes = null; 151 moduleSignatureType = null; 152 } 153 154 public void extractTo(File dst) throws IOException { 155 extractTo(dst, false); 156 } 157 158 public void extractTo(File dst, boolean deflate) throws IOException { 159 extractTo(dst, deflate, null, null, null); 160 } 161 162 public void extractTo(File dst, boolean deflate, File natlibs, 163 File natcmds, File configs) 164 throws IOException 165 { 166 this.deflate = deflate; 167 this.destination = dst != null ? dst.getCanonicalFile() : null; 168 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib"); 169 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin"); 170 this.configs = configs != null ? configs : new File(destination, "etc"); 171 172 try { 173 Files.store(moduleInfoBytes, computeRealPath("info")); 174 175 Event event = parser.event(); 176 if (event == END_FILE) 177 return; 178 179 if (event != START_SECTION) 180 throw new ModuleFileParserException( 181 "Expected START_SECTION, got : " + event); 182 // Module-Info and Signature, if present, have been consumed 183 do { 184 SectionHeader header = parser.getSectionHeader(); 185 SectionType type = header.getType(); 186 if (type.hasFiles()) { 187 while(parser.skipToNextStartSubSection()) { 188 readSubSection(type); 189 } 190 } else if (type == CLASSES) { 191 Iterator<Map.Entry<String,InputStream>> classes = 192 parser.getClasses(); 193 while (classes.hasNext()) { 194 Map.Entry<String,InputStream> entry = classes.next(); 195 try (OutputStream out = openOutputStream(type, entry.getKey())) { 196 copyStream(entry.getValue(), out); 197 } 198 } 199 // END_SECTION 200 parser.next(); 201 } else { 202 throw new IllegalArgumentException("Unknown type: " + type); 203 } 204 } while (parser.skipToNextStartSection()); 205 206 if (parser.event() != END_FILE) 207 throw new IOException("Expected END_FILE"); 208 } finally { 209 close(); 210 } 211 } 212 213 public byte[] getModuleInfoBytes() { 214 return moduleInfoBytes.clone(); 215 } 216 217 public byte[] getHash() { 218 return fileHeader.getHash(); 219 } 220 221 public List<byte[]> getCalculatedHashes() { 222 List<byte[]> hashes = new ArrayList<>(); 223 hashes.add(parser.getHeaderHash()); 224 for (Entry<SectionType,byte[]> entry : parser.getHashes().entrySet()) { 225 if (entry.getKey() != SIGNATURE) 226 hashes.add(entry.getValue()); 227 } 228 hashes.add(getHash()); 229 230 return hashes; 231 } 232 233 public boolean hasSignature() { 234 return moduleSignatureBytes != null; 235 } 236 237 public SignatureType getSignatureType() { 238 return moduleSignatureType; 239 } 240 241 public byte[] getSignature() { 242 return moduleSignatureBytes == null ? null : 243 moduleSignatureBytes.clone(); 244 } 245 246 /*package*/ byte[] getSignatureNoClone() { 247 return moduleSignatureBytes; 248 } 249 250 public void close() throws IOException { 251 try { 252 if (contentStream != null) { 253 contentStream.close(); 254 contentStream = null; 255 } 256 } finally { 257 if (filesWriter != null) { 258 filesWriter.close(); 259 filesWriter = null; 260 } 261 } 262 } 263 264 // subsections/files (resources, libs, cmds, configs) 265 public void readSubSection(SectionType type) throws IOException { 266 assert type == RESOURCES || type == NATIVE_LIBS || 267 type == NATIVE_CMDS || type == CONFIG; 268 269 SubSectionFileHeader subHeader = parser.getSubSectionFileHeader(); 270 String path = subHeader.getPath(); 271 try (OutputStream sink = openOutputStream(type, path)) { 272 copyStream(parser.getContentStream(), sink); 273 } 274 275 // post processing for executable and files outside the module dir 276 postExtract(type, currentPath); 277 } 278 279 private JarOutputStream contentStream = null; 280 281 private JarOutputStream contentStream() throws IOException { 282 if (contentStream != null) 283 return contentStream; 284 285 return contentStream = new JarOutputStream( 286 new BufferedOutputStream( 287 new FileOutputStream(computeRealPath("classes")))); 288 } 289 290 private File currentPath = null; 291 292 private OutputStream openOutputStream(SectionType type, String path) 293 throws IOException 294 { 295 currentPath = null; 296 if (type == CLASSES || type == RESOURCES) 297 return Files.newOutputStream(contentStream(), deflate, path); 298 currentPath = computeRealPath(type, path); 299 File parent = currentPath.getParentFile(); 300 if (!parent.exists()) 301 Files.mkdirs(parent, currentPath.getName()); 302 return new BufferedOutputStream(new FileOutputStream(currentPath)); 303 } 304 305 // Track files installed outside the module library. For later removal. 306 // files are relative to the modules directory. 307 private PrintWriter filesWriter; 308 309 private void trackFiles(File file) 310 throws IOException 311 { 312 if (file == null || file.toPath().startsWith(destination.toPath())) 313 return; 314 315 // Lazy construction, not all modules will need this. 316 if (filesWriter == null) 317 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8"); 318 319 filesWriter.println(Files.convertSeparator(relativize(destination, file))); 320 filesWriter.flush(); 321 } 322 323 List<IOException> remove() { 324 return ModuleFile.Reader.remove(destination); 325 } 326 327 // Removes a module, given its module install directory 328 static List<IOException> remove(File moduleDir) { 329 List<IOException> excs = new ArrayList<>(); 330 // Firstly remove any files installed outside of the module dir 331 File files = new File(moduleDir, "files"); 332 if (files.exists()) { 333 try { 334 List<String> filenames = 335 java.nio.file.Files.readAllLines(files.toPath(), 336 Charset.forName("UTF-8")); 337 for (String fn : filenames) { 338 try { 339 Files.delete(new File(moduleDir, 340 Files.platformSeparator(fn))); 341 } catch (IOException x) { 342 excs.add(x); 343 } 344 } 345 } catch (IOException x) { 346 excs.add(x); 347 } 348 } 349 350 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath())); 351 return excs; 352 } 353 354 // Returns the absolute path of the given section type. 355 private File getDirOfSection(SectionType type) { 356 if (type == NATIVE_LIBS) 357 return natlibs; 358 else if (type == NATIVE_CMDS) 359 return natcmds; 360 else if (type == CONFIG) 361 return configs; 362 363 // resolve sub dir section paths against the modules directory 364 return new File(destination, ModuleFile.getSubdirOfSection(type)); 365 } 366 367 private File computeRealPath(String path) throws IOException { 368 return resolveAndNormalize(destination, path); 369 } 370 371 private File computeRealPath(SectionType type, String storedpath) 372 throws IOException 373 { 374 File sectionPath = getDirOfSection(type); 375 File realpath = new File(sectionPath, 376 Files.ensureNonAbsolute(Files.platformSeparator(storedpath))); 377 378 validatePath(sectionPath, realpath); 379 380 // Create the parent directories if necessary 381 File parent = realpath.getParentFile(); 382 if (!parent.exists()) 383 Files.mkdirs(parent, realpath.getName()); 384 385 return realpath; 386 } 387 388 private static void markNativeCodeExecutable(SectionType type, 389 File file) 390 { 391 if (type == NATIVE_CMDS || (type == NATIVE_LIBS && 392 System.getProperty("os.name").startsWith("Windows"))) 393 file.setExecutable(true); 394 } 395 396 private void postExtract(SectionType type, File path) 397 throws IOException 398 { 399 markNativeCodeExecutable(type, path); 400 trackFiles(path); 401 } 402 } 403 404 private static void checkCompressor(SectionType type, 405 Compressor compressor) { 406 407 if ((MODULE_INFO == type && Compressor.NONE != compressor) || 408 (CLASSES == type && Compressor.PACK200_GZIP != compressor)) 409 throw new IllegalArgumentException(type 410 + " may not use compressor " 411 + compressor); 412 } 413 414 private static void checkSubsectionCount(SectionType type, 415 short subsections) { 416 if (!type.hasFiles() && subsections != 0) 417 throw new IllegalArgumentException(type 418 + " subsection count not 0: " 419 + subsections); 420 else if (type.hasFiles() && subsections == 0) 421 throw new IllegalArgumentException(type + " subsection count is 0"); 422 } 423 424 private static void copyStream(InputStream in, OutputStream out) 425 throws IOException 426 { 427 byte[] buf = new byte[8192]; 428 int read; 429 while ((read = in.read(buf)) > 0) 430 out.write(buf, 0, read); 431 } 432 433 private static void ensureNonNegativity(long size, String parameter) { 434 if (size < 0) 435 throw new IllegalArgumentException(parameter + "<0: " + size); 436 } 437 438 private static void ensureNonNull(Object reference, String parameter) { 439 if (null == reference) 440 throw new IllegalArgumentException(parameter + " == null"); 441 } 442 443 private static void ensureMatch(int found, int expected, String field) 444 throws IOException 445 { 446 if (found != expected) 447 throw new IOException(field + " expected : " 448 + Integer.toHexString(expected) + " found: " 449 + Integer.toHexString(found)); 450 } 451 452 private static void ensureShortNativePath(File path, String name) 453 throws IOException 454 { 455 // TODO: check for native code file in a stricter way 456 if (path.canExecute() 457 && name.indexOf('/') != -1) 458 throw new IOException("Native code path too long: " + path); 459 } 460 461 private static void ensureValidFileSize(long size, File path) 462 throws IOException 463 { 464 if (size < 0 || size > Integer.MAX_VALUE) 465 throw new IOException("File " + path + " too large: " + size); 466 } 467 468 static MessageDigest getHashInstance(HashType hashtype) 469 throws IOException 470 { 471 try { 472 switch(hashtype) { 473 case SHA256: 474 return MessageDigest.getInstance("SHA-256"); 475 default: 476 throw new IOException("Unknown hash type: " + hashtype); 477 } 478 } 479 catch (NoSuchAlgorithmException ex) { 480 throw new IOException(hashtype + " not found", ex); 481 } 482 } 483 484 private static short getMUTF8Length(String name) { 485 short size = 2; 486 487 for (int i = name.length()-1; i >= 0; i--) { 488 char ch = name.charAt(i); 489 490 if ('\u0001' <= ch && ch <= '\u007F') 491 size += 1; 492 else if ('\u0000' == ch 493 || '\u0080' <= ch && ch <= '\u07FF') 494 size += 2; 495 else 496 size += 3; 497 } 498 499 return size; 500 } 501 502 private static String hashHexString(byte[] hash) { 503 StringBuilder hex = new StringBuilder("0x"); 504 for (int i = 0; i < hash.length; i++) { 505 int val = (hash[i] & 0xFF); 506 if (val <= 16) 507 hex.append("0"); 508 hex.append(Integer.toHexString(val)); 509 } 510 return hex.toString(); 511 } 512 513 private static File resolveAndNormalize(File directory, String path) 514 throws IOException 515 { 516 File realpath = new File(directory, path); 517 if (directory != null && 518 ! realpath.toPath().startsWith(directory.toPath())) 519 throw new IOException("Bogus relative path: " + path); 520 521 return realpath; 522 } 523 524 525 private static String relativize(File directory, File path) throws IOException { 526 return (directory.toPath().relativize(path.toPath().toRealPath())).toString(); 527 } 528 529 private static void validatePath(File parent, File child) 530 throws IOException 531 { 532 if (!child.toPath().startsWith(parent.toPath()) ) 533 throw new IOException("Bogus relative path: " + child); 534 if (child.exists()) { 535 // conflict, for now just fail 536 throw new IOException("File " + child + " already exists"); 537 } 538 } 539 540 private static short readHashLength(DataInputStream in) throws IOException { 541 final short hashLength = in.readShort(); 542 ensureNonNegativity(hashLength, "hashLength"); 543 544 return hashLength; 545 } 546 547 private static byte[] readHashBytes(DataInputStream in, short hashLength) 548 throws IOException 549 { 550 final byte[] hash = new byte[hashLength]; 551 in.readFully(hash); 552 553 return hash; 554 } 555 556 private static byte[] readHash(DataInputStream in) throws IOException { 557 return readHashBytes(in, readHashLength(in)); 558 } 559 560 private static byte[] readFileHash(DigestInputStream dis) 561 throws IOException 562 { 563 DataInputStream in = new DataInputStream(dis); 564 565 final short hashLength = readHashLength(in); 566 567 // Turn digest computation off before reading the file hash 568 dis.on(false); 569 byte[] hash = readHashBytes(in, hashLength); 570 // Turn digest computation on again afterwards. 571 dis.on(true); 572 573 return hash; 574 } 575 576 /** 577 * <p> A module-file header </p> 578 */ 579 public final static class ModuleFileHeader { 580 public static final int LENGTH_WITHOUT_HASH = 30; 581 public static final int LENGTH = 582 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 583 584 // Fields are specified as unsigned. Treat signed values as bugs. 585 private final int magic; // MAGIC 586 private final FileConstants.Type type; // Type.MODULE_FILE 587 private final short major; // ModuleFile.MAJOR_VERSION 588 private final short minor; // ModuleFile.MINOR_VERSION 589 private final long csize; // Size of rest of file, compressed 590 private final long usize; // Space required for uncompressed contents 591 // (upper private final ound; need not be exact) 592 private final HashType hashType; // One of ModuleFile.HashType 593 // (applies final o all hashes in this file) 594 private final byte[] hash; // Hash of entire file (except this hash 595 // and the Signature section, if present) 596 597 public HashType getHashType() { 598 return hashType; 599 } 600 601 public byte[] getHash() { 602 return hash.clone(); 603 } 604 605 private byte[] getHashNoClone() { 606 return hash; 607 } 608 609 public long getCSize() { 610 return csize; 611 } 612 613 public long getUSize() { 614 return usize; 615 } 616 617 public ModuleFileHeader(long csize, long usize, 618 HashType hashType, byte[] hash) { 619 ensureNonNegativity(csize, "csize"); 620 ensureNonNegativity(usize, "usize"); 621 622 magic = FileConstants.MAGIC; 623 type = FileConstants.Type.MODULE_FILE; 624 major = MAJOR_VERSION; 625 minor = MINOR_VERSION; 626 627 this.csize = csize; 628 this.usize = usize; 629 this.hashType = hashType; 630 this.hash = hash.clone(); 631 } 632 633 public void write(final DataOutput out) throws IOException { 634 out.writeInt(magic); 635 out.writeShort(type.value()); 636 out.writeShort(major); 637 out.writeShort(minor); 638 out.writeLong(csize); 639 out.writeLong(usize); 640 out.writeShort(hashType.value()); 641 writeHash(out, hash); 642 } 643 644 public static ModuleFileHeader read(final DigestInputStream dis) 645 throws IOException 646 { 647 DataInputStream in = new DataInputStream(dis); 648 649 final int magic = in.readInt(); 650 ensureMatch(magic, FileConstants.MAGIC, 651 "FileConstants.MAGIC"); 652 653 final short type = in.readShort(); 654 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 655 "Type.MODULE_FILE"); 656 657 final short major = in.readShort(); 658 ensureMatch(major, MAJOR_VERSION, 659 "ModuleFile.MAJOR_VERSION"); 660 661 final short minor = in.readShort(); 662 ensureMatch(minor, MINOR_VERSION, 663 "ModuleFile.MINOR_VERSION"); 664 665 final long csize = in.readLong(); 666 final long usize = in.readLong(); 667 final short hashTypeValue = in.readShort(); 668 HashType hashType = null; 669 try { 670 hashType = HashType.valueOf(hashTypeValue); 671 } catch (IllegalArgumentException x) { 672 throw new IOException("Invalid hash type: " + hashTypeValue); 673 } 674 final byte[] hash = readFileHash(dis); 675 676 return new ModuleFileHeader(csize, usize, hashType, hash); 677 } 678 679 @Override 680 public String toString() { 681 return "MODULE{csize=" + csize + 682 ", hash=" + hashHexString(hash) + "}"; 683 } 684 } 685 686 /** 687 * <p> A module-file section header </p> 688 */ 689 public final static class SectionHeader { 690 public static final int LENGTH_WITHOUT_HASH = 12; 691 public static final int LENGTH = 692 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 693 694 // Fields are specified as unsigned. Treat signed values as bugs. 695 private final SectionType type; 696 private final Compressor compressor; 697 private final int csize; // Size of section content, compressed 698 private final short subsections; // Number of following subsections 699 private final byte[] hash; // Hash of section content 700 701 public SectionHeader(SectionType type, 702 Compressor compressor, 703 int csize, short subsections, byte[] hash) { 704 ensureNonNull(type, "type"); 705 ensureNonNull(compressor, "compressor"); 706 ensureNonNegativity(csize, "csize"); 707 ensureNonNegativity(subsections, "subsections"); 708 ensureNonNull(hash, "hash"); 709 checkSubsectionCount(type, subsections); 710 checkCompressor(type, compressor); 711 712 this.type = type; 713 this.compressor = compressor; 714 this.csize = csize; 715 this.subsections = subsections; 716 this.hash = hash.clone(); 717 } 718 719 public void write(DataOutput out) throws IOException { 720 out.writeShort(type.value()); 721 out.writeShort(compressor.value()); 722 out.writeInt(csize); 723 out.writeShort(subsections); 724 writeHash(out, hash); 725 } 726 727 private static SectionType lookupSectionType(short value) { 728 for (SectionType i : SectionType.class.getEnumConstants()) { 729 if (i.value() == value) return i; 730 } 731 732 throw new 733 IllegalArgumentException("No SectionType exists with value " 734 + value); 735 } 736 737 private static Compressor lookupCompressor(short value) { 738 for (Compressor i : Compressor.class.getEnumConstants()) { 739 if (i.value() == value) return i; 740 } 741 742 throw new 743 IllegalArgumentException("No Compressor exists with value " 744 + value); 745 } 746 747 public static SectionHeader read(DataInputStream in) throws IOException { 748 short tvalue = in.readShort(); 749 final SectionType type = lookupSectionType(tvalue); 750 short cvalue = in.readShort(); 751 final Compressor compressor = lookupCompressor(cvalue); 752 final int csize = in.readInt(); 753 final short sections = in.readShort(); 754 final byte[] hash = readHash(in); 755 756 return new SectionHeader(type, compressor, csize, 757 sections, hash); 758 } 759 760 public SectionType getType() { 761 return type; 762 } 763 764 public Compressor getCompressor() { 765 return compressor; 766 } 767 768 public int getCSize() { 769 return csize; 770 } 771 772 public short getSubsections() { 773 return subsections; 774 } 775 776 public byte[] getHash() { 777 return hash.clone(); 778 } 779 780 private byte[] getHashNoClone() { 781 return hash; 782 } 783 784 @Override 785 public String toString() { 786 return "SectionHeader{type= " + type 787 + ", compressor=" + compressor 788 + ", csize=" + csize 789 + ", subsections=" + subsections 790 + ", hash=" + hashHexString(hash) + "}"; 791 } 792 } 793 794 /** 795 * <p> A module-file sub-section header </p> 796 */ 797 public final static class SubSectionFileHeader { 798 private final int csize; // Size of file, compressed 799 private final String path; // Path name, in Java-modified UTF-8 800 801 public int getCSize() { 802 return csize; 803 } 804 805 public String getPath() { 806 return path; 807 } 808 809 public SubSectionFileHeader(int csize, String path) { 810 ensureNonNegativity(csize, "csize"); 811 ensureNonNull(path, "path"); 812 813 this.csize = csize; 814 this.path = path; 815 } 816 817 public void write(DataOutput out) throws IOException { 818 out.writeShort(SubSectionType.FILE.value()); 819 out.writeInt(csize); 820 out.writeUTF(path); 821 } 822 823 public static SubSectionFileHeader read(DataInputStream in) 824 throws IOException 825 { 826 final short type = in.readShort(); 827 ensureMatch(type, SubSectionType.FILE.value(), 828 "ModuleFile.SubSectionType.FILE"); 829 final int csize = in.readInt(); 830 final String path = in.readUTF(); 831 832 return new SubSectionFileHeader(csize, path); 833 } 834 } 835 836 /** 837 * <p> A module-file signature section </p> 838 */ 839 public final static class SignatureSection { 840 public static final int HEADER_LENGTH = 6; // type + signature length 841 842 private final int signatureType; // One of FileConstants.ModuleFile.HashType 843 private final int signatureLength; // Length of signature 844 private final byte[] signature; // Signature bytes 845 846 public int getSignatureType() { 847 return signatureType; 848 } 849 850 public int getSignatureLength() { 851 return signatureLength; 852 } 853 854 public byte[] getSignature() { 855 return signature; 856 } 857 858 public SignatureSection(int signatureType, int signatureLength, 859 byte[] signature) { 860 ensureNonNegativity(signatureLength, "signatureLength"); 861 862 this.signatureType = signatureType; 863 this.signatureLength = signatureLength; 864 this.signature = signature.clone(); 865 } 866 867 public void write(DataOutput out) throws IOException { 868 out.writeShort(signatureType); 869 out.writeInt(signatureLength); 870 out.write(signature); 871 } 872 873 public static SignatureSection read(DataInputStream in) 874 throws IOException 875 { 876 short signatureType = in.readShort(); 877 try { 878 SignatureType.valueOf(signatureType); 879 } catch (IllegalArgumentException x) { 880 throw new IOException("Invalid signature type: " + signatureType); 881 } 882 final int signatureLength = in.readInt(); 883 ensureNonNegativity(signatureLength, "signatureLength"); 884 final byte[] signature = new byte[signatureLength]; 885 in.readFully(signature); 886 return new SignatureSection(signatureType, signatureLength, 887 signature); 888 } 889 } 890 891 private static void writeHash(DataOutput out, byte[] hash) 892 throws IOException 893 { 894 out.writeShort(hash.length); 895 out.write(hash); 896 } 897 898 } --- EOF ---