1 /* 2 * Copyright (c) 1996, 2016, 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 sun.tools.jar; 27 28 import java.io.*; 29 import java.lang.module.ModuleDescriptor; 30 import java.lang.module.ModuleDescriptor.Exports; 31 import java.lang.module.ModuleDescriptor.Provides; 32 import java.lang.module.ModuleDescriptor.Requires; 33 import java.lang.module.ModuleDescriptor.Version; 34 import java.lang.module.ModuleFinder; 35 import java.lang.module.ModuleReference; 36 import java.lang.reflect.Method; 37 import java.net.URI; 38 import java.nio.file.Path; 39 import java.nio.file.Files; 40 import java.nio.file.Paths; 41 import java.util.*; 42 import java.util.function.Consumer; 43 import java.util.regex.Matcher; 44 import java.util.regex.Pattern; 45 import java.util.stream.Collectors; 46 import java.util.zip.*; 47 import java.util.jar.*; 48 import java.util.jar.Pack200.*; 49 import java.util.jar.Manifest; 50 import java.text.MessageFormat; 51 52 import jdk.internal.module.Hasher; 53 import jdk.internal.module.ModuleInfoExtender; 54 import jdk.internal.util.jar.JarIndex; 55 import static jdk.internal.util.jar.JarIndex.INDEX_NAME; 56 import static java.util.jar.JarFile.MANIFEST_NAME; 57 import static java.util.stream.Collectors.joining; 58 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 59 60 /** 61 * This class implements a simple utility for creating files in the JAR 62 * (Java Archive) file format. The JAR format is based on the ZIP file 63 * format, with optional meta-information stored in a MANIFEST entry. 64 */ 65 public 66 class Main { 67 String program; 68 PrintStream out, err; 69 String fname, mname, ename; 70 String zname = ""; 71 String[] files; 72 String rootjar = null; 73 74 // An entryName(path)->File map generated during "expand", it helps to 75 // decide whether or not an existing entry in a jar file needs to be 76 // replaced, during the "update" operation. 77 Map<String, File> entryMap = new HashMap<String, File>(); 78 79 // All files need to be added/updated. 80 Set<File> entries = new LinkedHashSet<File>(); 81 // All packages. 82 Set<String> packages = new HashSet<>(); 83 // All actual entries added, or existing, in the jar file ( excl manifest 84 // and module-info.class ). Populated during create or update. 85 Set<String> jarEntries = new HashSet<>(); 86 87 // Directories specified by "-C" operation. 88 Set<String> paths = new HashSet<String>(); 89 90 /* 91 * cflag: create 92 * uflag: update 93 * xflag: xtract 94 * tflag: table 95 * vflag: verbose 96 * flag0: no zip compression (store only) 97 * Mflag: DO NOT generate a manifest file (just ZIP) 98 * iflag: generate jar index 99 * nflag: Perform jar normalization at the end 100 * pflag: preserve/don't strip leading slash and .. component from file name 101 */ 102 boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag, nflag, pflag; 103 104 /* To support additional GNU Style informational options */ 105 enum Info { 106 HELP(GNUStyleOptions::printHelp), 107 COMPAT_HELP(GNUStyleOptions::printCompatHelp), 108 USAGE_SUMMARY(GNUStyleOptions::printUsageSummary), 109 VERSION(GNUStyleOptions::printVersion); 110 111 private Consumer<PrintStream> printFunction; 112 Info(Consumer<PrintStream> f) { this.printFunction = f; } 113 void print(PrintStream out) { printFunction.accept(out); } 114 }; 115 Info info; 116 117 /* Modular jar related options */ 118 boolean printModuleDescriptor; 119 Version moduleVersion; 120 Pattern dependenciesToHash; 121 ModuleFinder moduleFinder = ModuleFinder.empty(); 122 123 private static final String MODULE_INFO = "module-info.class"; 124 125 Path moduleInfo; 126 private boolean isModularJar() { return moduleInfo != null; } 127 128 static final String MANIFEST_DIR = "META-INF/"; 129 static final String VERSION = "1.0"; 130 131 private static ResourceBundle rsrc; 132 133 /** 134 * If true, maintain compatibility with JDK releases prior to 6.0 by 135 * timestamping extracted files with the time at which they are extracted. 136 * Default is to use the time given in the archive. 137 */ 138 private static final boolean useExtractionTime = 139 Boolean.getBoolean("sun.tools.jar.useExtractionTime"); 140 141 /** 142 * Initialize ResourceBundle 143 */ 144 static { 145 try { 146 rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar"); 147 } catch (MissingResourceException e) { 148 throw new Error("Fatal: Resource for jar is missing"); 149 } 150 } 151 152 static String getMsg(String key) { 153 try { 154 return (rsrc.getString(key)); 155 } catch (MissingResourceException e) { 156 throw new Error("Error in message file"); 157 } 158 } 159 160 static String formatMsg(String key, String arg) { 161 String msg = getMsg(key); 162 String[] args = new String[1]; 163 args[0] = arg; 164 return MessageFormat.format(msg, (Object[]) args); 165 } 166 167 static String formatMsg2(String key, String arg, String arg1) { 168 String msg = getMsg(key); 169 String[] args = new String[2]; 170 args[0] = arg; 171 args[1] = arg1; 172 return MessageFormat.format(msg, (Object[]) args); 173 } 174 175 public Main(PrintStream out, PrintStream err, String program) { 176 this.out = out; 177 this.err = err; 178 this.program = program; 179 } 180 181 /** 182 * Creates a new empty temporary file in the same directory as the 183 * specified file. A variant of File.createTempFile. 184 */ 185 private static File createTempFileInSameDirectoryAs(File file) 186 throws IOException { 187 File dir = file.getParentFile(); 188 if (dir == null) 189 dir = new File("."); 190 return File.createTempFile("jartmp", null, dir); 191 } 192 193 private boolean ok; 194 195 /** 196 * Starts main program with the specified arguments. 197 */ 198 public synchronized boolean run(String args[]) { 199 ok = true; 200 if (!parseArgs(args)) { 201 return false; 202 } 203 try { 204 if (cflag || uflag) { 205 if (fname != null) { 206 // The name of the zip file as it would appear as its own 207 // zip file entry. We use this to make sure that we don't 208 // add the zip file to itself. 209 zname = fname.replace(File.separatorChar, '/'); 210 if (zname.startsWith("./")) { 211 zname = zname.substring(2); 212 } 213 } 214 } 215 if (cflag) { 216 Manifest manifest = null; 217 InputStream in = null; 218 219 if (!Mflag) { 220 if (mname != null) { 221 in = new FileInputStream(mname); 222 manifest = new Manifest(new BufferedInputStream(in)); 223 } else { 224 manifest = new Manifest(); 225 } 226 addVersion(manifest); 227 addCreatedBy(manifest); 228 if (isAmbiguousMainClass(manifest)) { 229 if (in != null) { 230 in.close(); 231 } 232 return false; 233 } 234 if (ename != null) { 235 addMainClass(manifest, ename); 236 } 237 } 238 expand(null, files, false); 239 240 byte[] moduleInfoBytes = null; 241 if (isModularJar()) { 242 moduleInfoBytes = addExtendedModuleAttributes( 243 readModuleInfo(moduleInfo)); 244 } else if (moduleVersion != null || dependenciesToHash != null) { 245 error(getMsg("error.module.options.without.info")); 246 return false; 247 } 248 249 OutputStream out; 250 if (fname != null) { 251 out = new FileOutputStream(fname); 252 } else { 253 out = new FileOutputStream(FileDescriptor.out); 254 if (vflag) { 255 // Disable verbose output so that it does not appear 256 // on stdout along with file data 257 // error("Warning: -v option ignored"); 258 vflag = false; 259 } 260 } 261 File tmpfile = null; 262 final OutputStream finalout = out; 263 final String tmpbase = (fname == null) 264 ? "tmpjar" 265 : fname.substring(fname.indexOf(File.separatorChar) + 1); 266 if (nflag) { 267 tmpfile = createTemporaryFile(tmpbase, ".jar"); 268 out = new FileOutputStream(tmpfile); 269 } 270 create(new BufferedOutputStream(out, 4096), manifest, moduleInfoBytes); 271 272 // Consistency checks for modular jars. 273 if (isModularJar()) { 274 if (!checkServices(moduleInfoBytes)) 275 return false; 276 } 277 278 if (in != null) { 279 in.close(); 280 } 281 out.close(); 282 if (nflag) { 283 JarFile jarFile = null; 284 File packFile = null; 285 JarOutputStream jos = null; 286 try { 287 Packer packer = Pack200.newPacker(); 288 Map<String, String> p = packer.properties(); 289 p.put(Packer.EFFORT, "1"); // Minimal effort to conserve CPU 290 jarFile = new JarFile(tmpfile.getCanonicalPath()); 291 packFile = createTemporaryFile(tmpbase, ".pack"); 292 out = new FileOutputStream(packFile); 293 packer.pack(jarFile, out); 294 jos = new JarOutputStream(finalout); 295 Unpacker unpacker = Pack200.newUnpacker(); 296 unpacker.unpack(packFile, jos); 297 } catch (IOException ioe) { 298 fatalError(ioe); 299 } finally { 300 if (jarFile != null) { 301 jarFile.close(); 302 } 303 if (out != null) { 304 out.close(); 305 } 306 if (jos != null) { 307 jos.close(); 308 } 309 if (tmpfile != null && tmpfile.exists()) { 310 tmpfile.delete(); 311 } 312 if (packFile != null && packFile.exists()) { 313 packFile.delete(); 314 } 315 } 316 } 317 } else if (uflag) { 318 File inputFile = null, tmpFile = null; 319 FileInputStream in; 320 FileOutputStream out; 321 if (fname != null) { 322 inputFile = new File(fname); 323 tmpFile = createTempFileInSameDirectoryAs(inputFile); 324 in = new FileInputStream(inputFile); 325 out = new FileOutputStream(tmpFile); 326 } else { 327 in = new FileInputStream(FileDescriptor.in); 328 out = new FileOutputStream(FileDescriptor.out); 329 vflag = false; 330 } 331 InputStream manifest = (!Mflag && (mname != null)) ? 332 (new FileInputStream(mname)) : null; 333 expand(null, files, true); 334 335 byte[] moduleInfoBytes = null; 336 if (isModularJar()) { 337 moduleInfoBytes = readModuleInfo(moduleInfo); 338 } 339 340 boolean updateOk = update(in, new BufferedOutputStream(out), 341 manifest, moduleInfoBytes, null); 342 343 // Consistency checks for modular jars. 344 if (isModularJar()) { 345 if(!checkServices(moduleInfoBytes)) 346 return false; 347 } 348 349 if (ok) { 350 ok = updateOk; 351 } 352 in.close(); 353 out.close(); 354 if (manifest != null) { 355 manifest.close(); 356 } 357 if (ok && fname != null) { 358 // on Win32, we need this delete 359 inputFile.delete(); 360 if (!tmpFile.renameTo(inputFile)) { 361 tmpFile.delete(); 362 throw new IOException(getMsg("error.write.file")); 363 } 364 tmpFile.delete(); 365 } 366 } else if (tflag) { 367 replaceFSC(files); 368 // For the "list table contents" action, access using the 369 // ZipFile class is always most efficient since only a 370 // "one-finger" scan through the central directory is required. 371 if (fname != null) { 372 list(fname, files); 373 } else { 374 InputStream in = new FileInputStream(FileDescriptor.in); 375 try { 376 list(new BufferedInputStream(in), files); 377 } finally { 378 in.close(); 379 } 380 } 381 } else if (xflag) { 382 replaceFSC(files); 383 // For the extract action, when extracting all the entries, 384 // access using the ZipInputStream class is most efficient, 385 // since only a single sequential scan through the zip file is 386 // required. When using the ZipFile class, a "two-finger" scan 387 // is required, but this is likely to be more efficient when a 388 // partial extract is requested. In case the zip file has 389 // "leading garbage", we fall back from the ZipInputStream 390 // implementation to the ZipFile implementation, since only the 391 // latter can handle it. 392 if (fname != null && files != null) { 393 extract(fname, files); 394 } else { 395 InputStream in = (fname == null) 396 ? new FileInputStream(FileDescriptor.in) 397 : new FileInputStream(fname); 398 try { 399 if (!extract(new BufferedInputStream(in), files) && fname != null) { 400 extract(fname, files); 401 } 402 } finally { 403 in.close(); 404 } 405 } 406 } else if (iflag) { 407 genIndex(rootjar, files); 408 } else if (printModuleDescriptor) { 409 boolean found; 410 if (fname != null) { 411 found = printModuleDescriptor(new ZipFile(fname)); 412 } else { 413 try (FileInputStream fin = new FileInputStream(FileDescriptor.in)) { 414 found = printModuleDescriptor(fin); 415 } 416 } 417 if (!found) 418 error(getMsg("error.module.descriptor.not.found")); 419 } 420 } catch (IOException e) { 421 fatalError(e); 422 ok = false; 423 } catch (Error ee) { 424 ee.printStackTrace(); 425 ok = false; 426 } catch (Throwable t) { 427 t.printStackTrace(); 428 ok = false; 429 } 430 out.flush(); 431 err.flush(); 432 return ok; 433 } 434 435 /** 436 * Parses command line arguments. 437 */ 438 boolean parseArgs(String args[]) { 439 /* Preprocess and expand @file arguments */ 440 try { 441 args = CommandLine.parse(args); 442 } catch (FileNotFoundException e) { 443 fatalError(formatMsg("error.cant.open", e.getMessage())); 444 return false; 445 } catch (IOException e) { 446 fatalError(e); 447 return false; 448 } 449 /* parse flags */ 450 int count = 1; 451 try { 452 String flags = args[0]; 453 454 // Note: flags.length == 2 can be treated as the short version of 455 // the GNU option since the there cannot be any other options, 456 // excluding -C, as per the old way. 457 if (flags.startsWith("--") 458 || (flags.startsWith("-") && flags.length() == 2)) { 459 try { 460 count = GNUStyleOptions.parseOptions(this, args); 461 } catch (GNUStyleOptions.BadArgs x) { 462 if (info != null) { 463 info.print(out); 464 return true; 465 } 466 error(x.getMessage()); 467 if (x.showUsage) 468 Info.USAGE_SUMMARY.print(err); 469 return false; 470 } 471 } else { 472 // Legacy/compatibility options 473 if (flags.startsWith("-")) { 474 flags = flags.substring(1); 475 } 476 for (int i = 0; i < flags.length(); i++) { 477 switch (flags.charAt(i)) { 478 case 'c': 479 if (xflag || tflag || uflag || iflag) { 480 usageError(); 481 return false; 482 } 483 cflag = true; 484 break; 485 case 'u': 486 if (cflag || xflag || tflag || iflag) { 487 usageError(); 488 return false; 489 } 490 uflag = true; 491 break; 492 case 'x': 493 if (cflag || uflag || tflag || iflag) { 494 usageError(); 495 return false; 496 } 497 xflag = true; 498 break; 499 case 't': 500 if (cflag || uflag || xflag || iflag) { 501 usageError(); 502 return false; 503 } 504 tflag = true; 505 break; 506 case 'M': 507 Mflag = true; 508 break; 509 case 'v': 510 vflag = true; 511 break; 512 case 'f': 513 fname = args[count++]; 514 break; 515 case 'm': 516 mname = args[count++]; 517 break; 518 case '0': 519 flag0 = true; 520 break; 521 case 'i': 522 if (cflag || uflag || xflag || tflag) { 523 usageError(); 524 return false; 525 } 526 // do not increase the counter, files will contain rootjar 527 rootjar = args[count++]; 528 iflag = true; 529 break; 530 case 'n': 531 nflag = true; 532 break; 533 case 'e': 534 ename = args[count++]; 535 break; 536 case 'P': 537 pflag = true; 538 break; 539 default: 540 error(formatMsg("error.illegal.option", 541 String.valueOf(flags.charAt(i)))); 542 usageError(); 543 return false; 544 } 545 } 546 } 547 } catch (ArrayIndexOutOfBoundsException e) { 548 usageError(); 549 return false; 550 } 551 552 if (info != null) { 553 info.print(out); 554 return true; 555 } 556 557 if (!cflag && !tflag && !xflag && !uflag && !iflag && !printModuleDescriptor) { 558 error(getMsg("error.bad.option")); 559 usageError(); 560 return false; 561 } 562 /* parse file arguments */ 563 int n = args.length - count; 564 if (n > 0) { 565 int k = 0; 566 String[] nameBuf = new String[n]; 567 try { 568 for (int i = count; i < args.length; i++) { 569 if (args[i].equals("-C")) { 570 /* change the directory */ 571 String dir = args[++i]; 572 dir = (dir.endsWith(File.separator) ? 573 dir : (dir + File.separator)); 574 dir = dir.replace(File.separatorChar, '/'); 575 while (dir.indexOf("//") > -1) { 576 dir = dir.replace("//", "/"); 577 } 578 paths.add(dir.replace(File.separatorChar, '/')); 579 nameBuf[k++] = dir + args[++i]; 580 } else { 581 nameBuf[k++] = args[i]; 582 } 583 } 584 } catch (ArrayIndexOutOfBoundsException e) { 585 usageError(); 586 return false; 587 } 588 files = new String[k]; 589 System.arraycopy(nameBuf, 0, files, 0, k); 590 } else if (cflag && (mname == null)) { 591 error(getMsg("error.bad.cflag")); 592 usageError(); 593 return false; 594 } else if (uflag) { 595 if ((mname != null) || (ename != null)) { 596 /* just want to update the manifest */ 597 return true; 598 } else { 599 error(getMsg("error.bad.uflag")); 600 usageError(); 601 return false; 602 } 603 } 604 return true; 605 } 606 607 private static Set<String> findPackages(ZipFile zf) { 608 return zf.stream() 609 .filter(e -> e.getName().endsWith(".class")) 610 .map(e -> toPackageName(e)) 611 .filter(pkg -> pkg.length() > 0) 612 .distinct() 613 .collect(Collectors.toSet()); 614 } 615 616 private static String toPackageName(ZipEntry entry) { 617 return toPackageName(entry.getName()); 618 } 619 620 private static String toPackageName(String path) { 621 assert path.endsWith(".class"); 622 int index = path.lastIndexOf('/'); 623 if (index != -1) { 624 return path.substring(0, index).replace('/', '.'); 625 } else { 626 return ""; 627 } 628 } 629 630 /** 631 * Expands list of files to process into full list of all files that 632 * can be found by recursively descending directories. 633 */ 634 void expand(File dir, String[] files, boolean isUpdate) throws IOException { 635 if (files == null) 636 return; 637 638 for (int i = 0; i < files.length; i++) { 639 File f; 640 if (dir == null) 641 f = new File(files[i]); 642 else 643 f = new File(dir, files[i]); 644 645 if (f.isFile()) { 646 String path = f.getPath(); 647 if (entryName(path).equals(MODULE_INFO)) { 648 if (moduleInfo != null && vflag) 649 output(formatMsg("error.unexpected.module-info", path)); 650 moduleInfo = f.toPath(); 651 if (isUpdate) 652 entryMap.put(entryName(path), f); 653 } else if (entries.add(f)) { 654 jarEntries.add(entryName(path)); 655 if (path.endsWith(".class")) 656 packages.add(toPackageName(entryName(path))); 657 if (isUpdate) 658 entryMap.put(entryName(f.getPath()), f); 659 } 660 } else if (f.isDirectory()) { 661 if (entries.add(f)) { 662 if (isUpdate) { 663 String dirPath = f.getPath(); 664 dirPath = (dirPath.endsWith(File.separator)) ? dirPath : 665 (dirPath + File.separator); 666 entryMap.put(entryName(dirPath), f); 667 } 668 expand(f, f.list(), isUpdate); 669 } 670 } else { 671 error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); 672 ok = false; 673 } 674 } 675 } 676 677 /** 678 * Creates a new JAR file. 679 */ 680 void create(OutputStream out, Manifest manifest, byte[] moduleInfoBytes) 681 throws IOException 682 { 683 ZipOutputStream zos = new JarOutputStream(out); 684 if (flag0) { 685 zos.setMethod(ZipOutputStream.STORED); 686 } 687 // TODO: check module-info attributes against manifest ?? 688 if (manifest != null) { 689 if (vflag) { 690 output(getMsg("out.added.manifest")); 691 } 692 ZipEntry e = new ZipEntry(MANIFEST_DIR); 693 e.setTime(System.currentTimeMillis()); 694 e.setSize(0); 695 e.setCrc(0); 696 zos.putNextEntry(e); 697 e = new ZipEntry(MANIFEST_NAME); 698 e.setTime(System.currentTimeMillis()); 699 if (flag0) { 700 crc32Manifest(e, manifest); 701 } 702 zos.putNextEntry(e); 703 manifest.write(zos); 704 zos.closeEntry(); 705 } 706 if (moduleInfoBytes != null) { 707 if (vflag) { 708 output(getMsg("out.added.module-info")); 709 } 710 ZipEntry e = new ZipEntry(MODULE_INFO); 711 e.setTime(System.currentTimeMillis()); 712 if (flag0) { 713 crc32ModuleInfo(e, moduleInfoBytes); 714 } 715 zos.putNextEntry(e); 716 ByteArrayInputStream in = new ByteArrayInputStream(moduleInfoBytes); 717 in.transferTo(zos); 718 zos.closeEntry(); 719 } 720 for (File file: entries) { 721 addFile(zos, file); 722 } 723 zos.close(); 724 } 725 726 private char toUpperCaseASCII(char c) { 727 return (c < 'a' || c > 'z') ? c : (char) (c + 'A' - 'a'); 728 } 729 730 /** 731 * Compares two strings for equality, ignoring case. The second 732 * argument must contain only upper-case ASCII characters. 733 * We don't want case comparison to be locale-dependent (else we 734 * have the notorious "turkish i bug"). 735 */ 736 private boolean equalsIgnoreCase(String s, String upper) { 737 assert upper.toUpperCase(java.util.Locale.ENGLISH).equals(upper); 738 int len; 739 if ((len = s.length()) != upper.length()) 740 return false; 741 for (int i = 0; i < len; i++) { 742 char c1 = s.charAt(i); 743 char c2 = upper.charAt(i); 744 if (c1 != c2 && toUpperCaseASCII(c1) != c2) 745 return false; 746 } 747 return true; 748 } 749 750 /** 751 * Updates an existing jar file. 752 */ 753 boolean update(InputStream in, OutputStream out, 754 InputStream newManifest, 755 byte[] newModuleInfoBytes, 756 JarIndex jarIndex) throws IOException 757 { 758 ZipInputStream zis = new ZipInputStream(in); 759 ZipOutputStream zos = new JarOutputStream(out); 760 ZipEntry e = null; 761 boolean foundManifest = false; 762 boolean foundModuleInfo = false; 763 boolean updateOk = true; 764 765 if (jarIndex != null) { 766 addIndex(jarIndex, zos); 767 } 768 769 // put the old entries first, replace if necessary 770 while ((e = zis.getNextEntry()) != null) { 771 String name = e.getName(); 772 773 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME); 774 boolean isModuleInfoEntry = name.equals(MODULE_INFO); 775 776 if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) 777 || (Mflag && isManifestEntry)) { 778 continue; 779 } else if (isManifestEntry && ((newManifest != null) || 780 (ename != null))) { 781 foundManifest = true; 782 if (newManifest != null) { 783 // Don't read from the newManifest InputStream, as we 784 // might need it below, and we can't re-read the same data 785 // twice. 786 FileInputStream fis = new FileInputStream(mname); 787 boolean ambiguous = isAmbiguousMainClass(new Manifest(fis)); 788 fis.close(); 789 if (ambiguous) { 790 return false; 791 } 792 } 793 794 // Update the manifest. 795 Manifest old = new Manifest(zis); 796 if (newManifest != null) { 797 old.read(newManifest); 798 } 799 if (!updateManifest(old, zos)) { 800 return false; 801 } 802 } else if (isModuleInfoEntry 803 && ((newModuleInfoBytes != null) || (ename != null) 804 || moduleVersion != null || dependenciesToHash != null)) { 805 if (newModuleInfoBytes == null) { 806 // Update existing module-info.class 807 newModuleInfoBytes = readModuleInfo(zis); 808 } 809 } else { 810 if (!entryMap.containsKey(name)) { // copy the old stuff 811 // do our own compression 812 ZipEntry e2 = new ZipEntry(name); 813 e2.setMethod(e.getMethod()); 814 e2.setTime(e.getTime()); 815 e2.setComment(e.getComment()); 816 e2.setExtra(e.getExtra()); 817 if (e.getMethod() == ZipEntry.STORED) { 818 e2.setSize(e.getSize()); 819 e2.setCrc(e.getCrc()); 820 } 821 zos.putNextEntry(e2); 822 copy(zis, zos); 823 } else { // replace with the new files 824 File f = entryMap.get(name); 825 addFile(zos, f); 826 entryMap.remove(name); 827 entries.remove(f); 828 } 829 830 jarEntries.add(name); 831 if (name.endsWith(".class")) 832 packages.add(toPackageName(name)); 833 } 834 } 835 836 // add the remaining new files 837 for (File f: entries) { 838 addFile(zos, f); 839 } 840 if (!foundManifest) { 841 if (newManifest != null) { 842 Manifest m = new Manifest(newManifest); 843 updateOk = !isAmbiguousMainClass(m); 844 if (updateOk) { 845 if (!updateManifest(m, zos)) { 846 updateOk = false; 847 } 848 } 849 } else if (ename != null) { 850 if (!updateManifest(new Manifest(), zos)) { 851 updateOk = false; 852 } 853 } 854 } 855 856 // write the module-info.class 857 if (newModuleInfoBytes != null) { 858 newModuleInfoBytes = addExtendedModuleAttributes(newModuleInfoBytes); 859 860 // TODO: check manifest main classes, etc 861 if (!updateModuleInfo(newModuleInfoBytes, zos)) { 862 updateOk = false; 863 } 864 } else if (moduleVersion != null || dependenciesToHash != null) { 865 error(getMsg("error.module.options.without.info")); 866 updateOk = false; 867 } 868 869 zis.close(); 870 zos.close(); 871 return updateOk; 872 } 873 874 875 private void addIndex(JarIndex index, ZipOutputStream zos) 876 throws IOException 877 { 878 ZipEntry e = new ZipEntry(INDEX_NAME); 879 e.setTime(System.currentTimeMillis()); 880 if (flag0) { 881 CRC32OutputStream os = new CRC32OutputStream(); 882 index.write(os); 883 os.updateEntry(e); 884 } 885 zos.putNextEntry(e); 886 index.write(zos); 887 zos.closeEntry(); 888 } 889 890 private boolean updateModuleInfo(byte[] moduleInfoBytes, ZipOutputStream zos) 891 throws IOException 892 { 893 ZipEntry e = new ZipEntry(MODULE_INFO); 894 e.setTime(System.currentTimeMillis()); 895 if (flag0) { 896 crc32ModuleInfo(e, moduleInfoBytes); 897 } 898 zos.putNextEntry(e); 899 zos.write(moduleInfoBytes); 900 if (vflag) { 901 output(getMsg("out.update.module-info")); 902 } 903 return true; 904 } 905 906 private boolean updateManifest(Manifest m, ZipOutputStream zos) 907 throws IOException 908 { 909 addVersion(m); 910 addCreatedBy(m); 911 if (ename != null) { 912 addMainClass(m, ename); 913 } 914 ZipEntry e = new ZipEntry(MANIFEST_NAME); 915 e.setTime(System.currentTimeMillis()); 916 if (flag0) { 917 crc32Manifest(e, m); 918 } 919 zos.putNextEntry(e); 920 m.write(zos); 921 if (vflag) { 922 output(getMsg("out.update.manifest")); 923 } 924 return true; 925 } 926 927 private static final boolean isWinDriveLetter(char c) { 928 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 929 } 930 931 private String safeName(String name) { 932 if (!pflag) { 933 int len = name.length(); 934 int i = name.lastIndexOf("../"); 935 if (i == -1) { 936 i = 0; 937 } else { 938 i += 3; // strip any dot-dot components 939 } 940 if (File.separatorChar == '\\') { 941 // the spec requests no drive letter. skip if 942 // the entry name has one. 943 while (i < len) { 944 int off = i; 945 if (i + 1 < len && 946 name.charAt(i + 1) == ':' && 947 isWinDriveLetter(name.charAt(i))) { 948 i += 2; 949 } 950 while (i < len && name.charAt(i) == '/') { 951 i++; 952 } 953 if (i == off) { 954 break; 955 } 956 } 957 } else { 958 while (i < len && name.charAt(i) == '/') { 959 i++; 960 } 961 } 962 if (i != 0) { 963 name = name.substring(i); 964 } 965 } 966 return name; 967 } 968 969 private String entryName(String name) { 970 name = name.replace(File.separatorChar, '/'); 971 String matchPath = ""; 972 for (String path : paths) { 973 if (name.startsWith(path) 974 && (path.length() > matchPath.length())) { 975 matchPath = path; 976 } 977 } 978 name = safeName(name.substring(matchPath.length())); 979 // the old implementaton doesn't remove 980 // "./" if it was led by "/" (?) 981 if (name.startsWith("./")) { 982 name = name.substring(2); 983 } 984 return name; 985 } 986 987 private void addVersion(Manifest m) { 988 Attributes global = m.getMainAttributes(); 989 if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) { 990 global.put(Attributes.Name.MANIFEST_VERSION, VERSION); 991 } 992 } 993 994 private void addCreatedBy(Manifest m) { 995 Attributes global = m.getMainAttributes(); 996 if (global.getValue(new Attributes.Name("Created-By")) == null) { 997 String javaVendor = System.getProperty("java.vendor"); 998 String jdkVersion = System.getProperty("java.version"); 999 global.put(new Attributes.Name("Created-By"), jdkVersion + " (" + 1000 javaVendor + ")"); 1001 } 1002 } 1003 1004 private void addMainClass(Manifest m, String mainApp) { 1005 Attributes global = m.getMainAttributes(); 1006 1007 // overrides any existing Main-Class attribute 1008 global.put(Attributes.Name.MAIN_CLASS, mainApp); 1009 } 1010 1011 private boolean isAmbiguousMainClass(Manifest m) { 1012 if (ename != null) { 1013 Attributes global = m.getMainAttributes(); 1014 if ((global.get(Attributes.Name.MAIN_CLASS) != null)) { 1015 error(getMsg("error.bad.eflag")); 1016 usageError(); 1017 return true; 1018 } 1019 } 1020 return false; 1021 } 1022 1023 /** 1024 * Adds a new file entry to the ZIP output stream. 1025 */ 1026 void addFile(ZipOutputStream zos, File file) throws IOException { 1027 String name = file.getPath(); 1028 boolean isDir = file.isDirectory(); 1029 if (isDir) { 1030 name = name.endsWith(File.separator) ? name : 1031 (name + File.separator); 1032 } 1033 name = entryName(name); 1034 1035 if (name.equals("") || name.equals(".") || name.equals(zname)) { 1036 return; 1037 } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST_NAME)) 1038 && !Mflag) { 1039 if (vflag) { 1040 output(formatMsg("out.ignore.entry", name)); 1041 } 1042 return; 1043 } else if (name.equals(MODULE_INFO)) { 1044 throw new Error("Unexpected module info: " + name); 1045 } 1046 1047 long size = isDir ? 0 : file.length(); 1048 1049 if (vflag) { 1050 out.print(formatMsg("out.adding", name)); 1051 } 1052 ZipEntry e = new ZipEntry(name); 1053 e.setTime(file.lastModified()); 1054 if (size == 0) { 1055 e.setMethod(ZipEntry.STORED); 1056 e.setSize(0); 1057 e.setCrc(0); 1058 } else if (flag0) { 1059 crc32File(e, file); 1060 } 1061 zos.putNextEntry(e); 1062 if (!isDir) { 1063 copy(file, zos); 1064 } 1065 zos.closeEntry(); 1066 /* report how much compression occurred. */ 1067 if (vflag) { 1068 size = e.getSize(); 1069 long csize = e.getCompressedSize(); 1070 out.print(formatMsg2("out.size", String.valueOf(size), 1071 String.valueOf(csize))); 1072 if (e.getMethod() == ZipEntry.DEFLATED) { 1073 long ratio = 0; 1074 if (size != 0) { 1075 ratio = ((size - csize) * 100) / size; 1076 } 1077 output(formatMsg("out.deflated", String.valueOf(ratio))); 1078 } else { 1079 output(getMsg("out.stored")); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * A buffer for use only by copy(InputStream, OutputStream). 1086 * Not as clean as allocating a new buffer as needed by copy, 1087 * but significantly more efficient. 1088 */ 1089 private byte[] copyBuf = new byte[8192]; 1090 1091 /** 1092 * Copies all bytes from the input stream to the output stream. 1093 * Does not close or flush either stream. 1094 * 1095 * @param from the input stream to read from 1096 * @param to the output stream to write to 1097 * @throws IOException if an I/O error occurs 1098 */ 1099 private void copy(InputStream from, OutputStream to) throws IOException { 1100 int n; 1101 while ((n = from.read(copyBuf)) != -1) 1102 to.write(copyBuf, 0, n); 1103 } 1104 1105 /** 1106 * Copies all bytes from the input file to the output stream. 1107 * Does not close or flush the output stream. 1108 * 1109 * @param from the input file to read from 1110 * @param to the output stream to write to 1111 * @throws IOException if an I/O error occurs 1112 */ 1113 private void copy(File from, OutputStream to) throws IOException { 1114 InputStream in = new FileInputStream(from); 1115 try { 1116 copy(in, to); 1117 } finally { 1118 in.close(); 1119 } 1120 } 1121 1122 /** 1123 * Copies all bytes from the input stream to the output file. 1124 * Does not close the input stream. 1125 * 1126 * @param from the input stream to read from 1127 * @param to the output file to write to 1128 * @throws IOException if an I/O error occurs 1129 */ 1130 private void copy(InputStream from, File to) throws IOException { 1131 OutputStream out = new FileOutputStream(to); 1132 try { 1133 copy(from, out); 1134 } finally { 1135 out.close(); 1136 } 1137 } 1138 1139 /** 1140 * Computes the crc32 of a module-info.class. This is necessary when the 1141 * ZipOutputStream is in STORED mode. 1142 */ 1143 private void crc32ModuleInfo(ZipEntry e, byte[] bytes) throws IOException { 1144 CRC32OutputStream os = new CRC32OutputStream(); 1145 ByteArrayInputStream in = new ByteArrayInputStream(bytes); 1146 in.transferTo(os); 1147 os.updateEntry(e); 1148 } 1149 1150 /** 1151 * Computes the crc32 of a Manifest. This is necessary when the 1152 * ZipOutputStream is in STORED mode. 1153 */ 1154 private void crc32Manifest(ZipEntry e, Manifest m) throws IOException { 1155 CRC32OutputStream os = new CRC32OutputStream(); 1156 m.write(os); 1157 os.updateEntry(e); 1158 } 1159 1160 /** 1161 * Computes the crc32 of a File. This is necessary when the 1162 * ZipOutputStream is in STORED mode. 1163 */ 1164 private void crc32File(ZipEntry e, File f) throws IOException { 1165 CRC32OutputStream os = new CRC32OutputStream(); 1166 copy(f, os); 1167 if (os.n != f.length()) { 1168 throw new JarException(formatMsg( 1169 "error.incorrect.length", f.getPath())); 1170 } 1171 os.updateEntry(e); 1172 } 1173 1174 void replaceFSC(String files[]) { 1175 if (files != null) { 1176 for (int i = 0; i < files.length; i++) { 1177 files[i] = files[i].replace(File.separatorChar, '/'); 1178 } 1179 } 1180 } 1181 1182 @SuppressWarnings("serial") 1183 Set<ZipEntry> newDirSet() { 1184 return new HashSet<ZipEntry>() { 1185 public boolean add(ZipEntry e) { 1186 return ((e == null || useExtractionTime) ? false : super.add(e)); 1187 }}; 1188 } 1189 1190 void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException { 1191 for (ZipEntry ze : zes) { 1192 long lastModified = ze.getTime(); 1193 if (lastModified != -1) { 1194 String name = safeName(ze.getName().replace(File.separatorChar, '/')); 1195 if (name.length() != 0) { 1196 File f = new File(name.replace('/', File.separatorChar)); 1197 f.setLastModified(lastModified); 1198 } 1199 } 1200 } 1201 } 1202 1203 /** 1204 * Extracts specified entries from JAR file. 1205 * 1206 * @return whether entries were found and successfully extracted 1207 * (indicating this was a zip file without "leading garbage") 1208 */ 1209 boolean extract(InputStream in, String files[]) throws IOException { 1210 ZipInputStream zis = new ZipInputStream(in); 1211 ZipEntry e; 1212 // Set of all directory entries specified in archive. Disallows 1213 // null entries. Disallows all entries if using pre-6.0 behavior. 1214 boolean entriesFound = false; 1215 Set<ZipEntry> dirs = newDirSet(); 1216 while ((e = zis.getNextEntry()) != null) { 1217 entriesFound = true; 1218 if (files == null) { 1219 dirs.add(extractFile(zis, e)); 1220 } else { 1221 String name = e.getName(); 1222 for (String file : files) { 1223 if (name.startsWith(file)) { 1224 dirs.add(extractFile(zis, e)); 1225 break; 1226 } 1227 } 1228 } 1229 } 1230 1231 // Update timestamps of directories specified in archive with their 1232 // timestamps as given in the archive. We do this after extraction, 1233 // instead of during, because creating a file in a directory changes 1234 // that directory's timestamp. 1235 updateLastModifiedTime(dirs); 1236 1237 return entriesFound; 1238 } 1239 1240 /** 1241 * Extracts specified entries from JAR file, via ZipFile. 1242 */ 1243 void extract(String fname, String files[]) throws IOException { 1244 ZipFile zf = new ZipFile(fname); 1245 Set<ZipEntry> dirs = newDirSet(); 1246 Enumeration<? extends ZipEntry> zes = zf.entries(); 1247 while (zes.hasMoreElements()) { 1248 ZipEntry e = zes.nextElement(); 1249 if (files == null) { 1250 dirs.add(extractFile(zf.getInputStream(e), e)); 1251 } else { 1252 String name = e.getName(); 1253 for (String file : files) { 1254 if (name.startsWith(file)) { 1255 dirs.add(extractFile(zf.getInputStream(e), e)); 1256 break; 1257 } 1258 } 1259 } 1260 } 1261 zf.close(); 1262 updateLastModifiedTime(dirs); 1263 } 1264 1265 /** 1266 * Extracts next entry from JAR file, creating directories as needed. If 1267 * the entry is for a directory which doesn't exist prior to this 1268 * invocation, returns that entry, otherwise returns null. 1269 */ 1270 ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException { 1271 ZipEntry rc = null; 1272 // The spec requres all slashes MUST be forward '/', it is possible 1273 // an offending zip/jar entry may uses the backwards slash in its 1274 // name. It might cause problem on Windows platform as it skips 1275 // our "safe" check for leading slahs and dot-dot. So replace them 1276 // with '/'. 1277 String name = safeName(e.getName().replace(File.separatorChar, '/')); 1278 if (name.length() == 0) { 1279 return rc; // leading '/' or 'dot-dot' only path 1280 } 1281 File f = new File(name.replace('/', File.separatorChar)); 1282 if (e.isDirectory()) { 1283 if (f.exists()) { 1284 if (!f.isDirectory()) { 1285 throw new IOException(formatMsg("error.create.dir", 1286 f.getPath())); 1287 } 1288 } else { 1289 if (!f.mkdirs()) { 1290 throw new IOException(formatMsg("error.create.dir", 1291 f.getPath())); 1292 } else { 1293 rc = e; 1294 } 1295 } 1296 1297 if (vflag) { 1298 output(formatMsg("out.create", name)); 1299 } 1300 } else { 1301 if (f.getParent() != null) { 1302 File d = new File(f.getParent()); 1303 if (!d.exists() && !d.mkdirs() || !d.isDirectory()) { 1304 throw new IOException(formatMsg( 1305 "error.create.dir", d.getPath())); 1306 } 1307 } 1308 try { 1309 copy(is, f); 1310 } finally { 1311 if (is instanceof ZipInputStream) 1312 ((ZipInputStream)is).closeEntry(); 1313 else 1314 is.close(); 1315 } 1316 if (vflag) { 1317 if (e.getMethod() == ZipEntry.DEFLATED) { 1318 output(formatMsg("out.inflated", name)); 1319 } else { 1320 output(formatMsg("out.extracted", name)); 1321 } 1322 } 1323 } 1324 if (!useExtractionTime) { 1325 long lastModified = e.getTime(); 1326 if (lastModified != -1) { 1327 f.setLastModified(lastModified); 1328 } 1329 } 1330 return rc; 1331 } 1332 1333 /** 1334 * Lists contents of JAR file. 1335 */ 1336 void list(InputStream in, String files[]) throws IOException { 1337 ZipInputStream zis = new ZipInputStream(in); 1338 ZipEntry e; 1339 while ((e = zis.getNextEntry()) != null) { 1340 /* 1341 * In the case of a compressed (deflated) entry, the entry size 1342 * is stored immediately following the entry data and cannot be 1343 * determined until the entry is fully read. Therefore, we close 1344 * the entry first before printing out its attributes. 1345 */ 1346 zis.closeEntry(); 1347 printEntry(e, files); 1348 } 1349 } 1350 1351 /** 1352 * Lists contents of JAR file, via ZipFile. 1353 */ 1354 void list(String fname, String files[]) throws IOException { 1355 ZipFile zf = new ZipFile(fname); 1356 Enumeration<? extends ZipEntry> zes = zf.entries(); 1357 while (zes.hasMoreElements()) { 1358 printEntry(zes.nextElement(), files); 1359 } 1360 zf.close(); 1361 } 1362 1363 /** 1364 * Outputs the class index table to the INDEX.LIST file of the 1365 * root jar file. 1366 */ 1367 void dumpIndex(String rootjar, JarIndex index) throws IOException { 1368 File jarFile = new File(rootjar); 1369 Path jarPath = jarFile.toPath(); 1370 Path tmpPath = createTempFileInSameDirectoryAs(jarFile).toPath(); 1371 try { 1372 if (update(Files.newInputStream(jarPath), 1373 Files.newOutputStream(tmpPath), 1374 null, null, index)) { 1375 try { 1376 Files.move(tmpPath, jarPath, REPLACE_EXISTING); 1377 } catch (IOException e) { 1378 throw new IOException(getMsg("error.write.file"), e); 1379 } 1380 } 1381 } finally { 1382 Files.deleteIfExists(tmpPath); 1383 } 1384 } 1385 1386 private HashSet<String> jarPaths = new HashSet<String>(); 1387 1388 /** 1389 * Generates the transitive closure of the Class-Path attribute for 1390 * the specified jar file. 1391 */ 1392 List<String> getJarPath(String jar) throws IOException { 1393 List<String> files = new ArrayList<String>(); 1394 files.add(jar); 1395 jarPaths.add(jar); 1396 1397 // take out the current path 1398 String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1)); 1399 1400 // class path attribute will give us jar file name with 1401 // '/' as separators, so we need to change them to the 1402 // appropriate one before we open the jar file. 1403 JarFile rf = new JarFile(jar.replace('/', File.separatorChar)); 1404 1405 if (rf != null) { 1406 Manifest man = rf.getManifest(); 1407 if (man != null) { 1408 Attributes attr = man.getMainAttributes(); 1409 if (attr != null) { 1410 String value = attr.getValue(Attributes.Name.CLASS_PATH); 1411 if (value != null) { 1412 StringTokenizer st = new StringTokenizer(value); 1413 while (st.hasMoreTokens()) { 1414 String ajar = st.nextToken(); 1415 if (!ajar.endsWith("/")) { // it is a jar file 1416 ajar = path.concat(ajar); 1417 /* check on cyclic dependency */ 1418 if (! jarPaths.contains(ajar)) { 1419 files.addAll(getJarPath(ajar)); 1420 } 1421 } 1422 } 1423 } 1424 } 1425 } 1426 } 1427 rf.close(); 1428 return files; 1429 } 1430 1431 /** 1432 * Generates class index file for the specified root jar file. 1433 */ 1434 void genIndex(String rootjar, String[] files) throws IOException { 1435 List<String> jars = getJarPath(rootjar); 1436 int njars = jars.size(); 1437 String[] jarfiles; 1438 1439 if (njars == 1 && files != null) { 1440 // no class-path attribute defined in rootjar, will 1441 // use command line specified list of jars 1442 for (int i = 0; i < files.length; i++) { 1443 jars.addAll(getJarPath(files[i])); 1444 } 1445 njars = jars.size(); 1446 } 1447 jarfiles = jars.toArray(new String[njars]); 1448 JarIndex index = new JarIndex(jarfiles); 1449 dumpIndex(rootjar, index); 1450 } 1451 1452 /** 1453 * Prints entry information, if requested. 1454 */ 1455 void printEntry(ZipEntry e, String[] files) throws IOException { 1456 if (files == null) { 1457 printEntry(e); 1458 } else { 1459 String name = e.getName(); 1460 for (String file : files) { 1461 if (name.startsWith(file)) { 1462 printEntry(e); 1463 return; 1464 } 1465 } 1466 } 1467 } 1468 1469 /** 1470 * Prints entry information. 1471 */ 1472 void printEntry(ZipEntry e) throws IOException { 1473 if (vflag) { 1474 StringBuilder sb = new StringBuilder(); 1475 String s = Long.toString(e.getSize()); 1476 for (int i = 6 - s.length(); i > 0; --i) { 1477 sb.append(' '); 1478 } 1479 sb.append(s).append(' ').append(new Date(e.getTime()).toString()); 1480 sb.append(' ').append(e.getName()); 1481 output(sb.toString()); 1482 } else { 1483 output(e.getName()); 1484 } 1485 } 1486 1487 /** 1488 * Prints usage message. 1489 */ 1490 void usageError() { 1491 Info.USAGE_SUMMARY.print(err); 1492 } 1493 1494 /** 1495 * A fatal exception has been caught. No recovery possible 1496 */ 1497 void fatalError(Exception e) { 1498 e.printStackTrace(); 1499 } 1500 1501 /** 1502 * A fatal condition has been detected; message is "s". 1503 * No recovery possible 1504 */ 1505 void fatalError(String s) { 1506 error(program + ": " + s); 1507 } 1508 1509 /** 1510 * Print an output message; like verbose output and the like 1511 */ 1512 protected void output(String s) { 1513 out.println(s); 1514 } 1515 1516 /** 1517 * Print an error message; like something is broken 1518 */ 1519 protected void error(String s) { 1520 err.println(s); 1521 } 1522 1523 /** 1524 * Main routine to start program. 1525 */ 1526 public static void main(String args[]) { 1527 Main jartool = new Main(System.out, System.err, "jar"); 1528 System.exit(jartool.run(args) ? 0 : 1); 1529 } 1530 1531 /** 1532 * An OutputStream that doesn't send its output anywhere, (but could). 1533 * It's here to find the CRC32 of an input file, necessary for STORED 1534 * mode in ZIP. 1535 */ 1536 private static class CRC32OutputStream extends java.io.OutputStream { 1537 final CRC32 crc = new CRC32(); 1538 long n = 0; 1539 1540 CRC32OutputStream() {} 1541 1542 public void write(int r) throws IOException { 1543 crc.update(r); 1544 n++; 1545 } 1546 1547 public void write(byte[] b, int off, int len) throws IOException { 1548 crc.update(b, off, len); 1549 n += len; 1550 } 1551 1552 /** 1553 * Updates a ZipEntry which describes the data read by this 1554 * output stream, in STORED mode. 1555 */ 1556 public void updateEntry(ZipEntry e) { 1557 e.setMethod(ZipEntry.STORED); 1558 e.setSize(n); 1559 e.setCrc(crc.getValue()); 1560 } 1561 } 1562 1563 /** 1564 * Attempt to create temporary file in the system-provided temporary folder, if failed attempts 1565 * to create it in the same folder as the file in parameter (if any) 1566 */ 1567 private File createTemporaryFile(String tmpbase, String suffix) { 1568 File tmpfile = null; 1569 1570 try { 1571 tmpfile = File.createTempFile(tmpbase, suffix); 1572 } catch (IOException | SecurityException e) { 1573 // Unable to create file due to permission violation or security exception 1574 } 1575 if (tmpfile == null) { 1576 // Were unable to create temporary file, fall back to temporary file in the same folder 1577 if (fname != null) { 1578 try { 1579 File tmpfolder = new File(fname).getAbsoluteFile().getParentFile(); 1580 tmpfile = File.createTempFile(fname, ".tmp" + suffix, tmpfolder); 1581 } catch (IOException ioe) { 1582 // Last option failed - fall gracefully 1583 fatalError(ioe); 1584 } 1585 } else { 1586 // No options left - we can not compress to stdout without access to the temporary folder 1587 fatalError(new IOException(getMsg("error.create.tempfile"))); 1588 } 1589 } 1590 return tmpfile; 1591 } 1592 1593 private static byte[] readModuleInfo(InputStream zis) throws IOException { 1594 return zis.readAllBytes(); 1595 } 1596 1597 private static byte[] readModuleInfo(Path path) throws IOException { 1598 try (InputStream is = Files.newInputStream(path)) { 1599 return is.readAllBytes(); 1600 } 1601 } 1602 1603 // Modular jar support 1604 1605 static <T> String toString(Set<T> set, 1606 CharSequence prefix, 1607 CharSequence suffix ) { 1608 if (set.isEmpty()) 1609 return ""; 1610 1611 return set.stream().map(e -> e.toString()) 1612 .collect(joining(", ", prefix, suffix)); 1613 } 1614 1615 private boolean printModuleDescriptor(ZipFile zipFile) 1616 throws IOException 1617 { 1618 ZipEntry entry = zipFile.getEntry(MODULE_INFO); 1619 if (entry == null) 1620 return false; 1621 1622 try (InputStream is = zipFile.getInputStream(entry)) { 1623 printModuleDescriptor(is); 1624 } 1625 return true; 1626 } 1627 1628 private boolean printModuleDescriptor(FileInputStream fis) 1629 throws IOException 1630 { 1631 try (BufferedInputStream bis = new BufferedInputStream(fis); 1632 ZipInputStream zis = new ZipInputStream(bis)) { 1633 1634 ZipEntry e; 1635 while ((e = zis.getNextEntry()) != null) { 1636 if (e.getName().equals(MODULE_INFO)) { 1637 printModuleDescriptor(zis); 1638 return true; 1639 } 1640 } 1641 } 1642 return false; 1643 } 1644 1645 @SuppressWarnings("unchecked") 1646 private void printModuleDescriptor(InputStream entryInputStream) 1647 throws IOException 1648 { 1649 ModuleDescriptor md = ModuleDescriptor.read(entryInputStream); 1650 StringBuilder sb = new StringBuilder(); 1651 sb.append("\nName:\n " + md.toNameAndVersion()); 1652 1653 Set<Requires> requires = md.requires(); 1654 if (!requires.isEmpty()) { 1655 sb.append("\nRequires:"); 1656 requires.forEach(r -> 1657 sb.append("\n ").append(r.name()) 1658 .append(toString(r.modifiers(), " [ ", " ]"))); 1659 } 1660 1661 Set<String> s = md.uses(); 1662 if (!s.isEmpty()) { 1663 sb.append("\nUses: "); 1664 s.forEach(sv -> sb.append("\n ").append(sv)); 1665 } 1666 1667 Set<Exports> exports = md.exports(); 1668 if (!exports.isEmpty()) { 1669 sb.append("\nExports:"); 1670 exports.forEach(sv -> sb.append("\n ").append(sv)); 1671 } 1672 1673 Map<String,Provides> provides = md.provides(); 1674 if (!provides.isEmpty()) { 1675 sb.append("\nProvides: "); 1676 provides.values().forEach(p -> 1677 sb.append("\n ").append(p.service()) 1678 .append(" with ") 1679 .append(toString(p.providers(), "", ""))); 1680 } 1681 1682 Optional<String> mc = md.mainClass(); 1683 if (mc.isPresent()) 1684 sb.append("\nMain class:\n " + mc.get()); 1685 1686 s = md.conceals(); 1687 if (!s.isEmpty()) { 1688 sb.append("\nConceals:"); 1689 s.forEach(p -> sb.append("\n ").append(p)); 1690 } 1691 1692 try { 1693 Method m = ModuleDescriptor.class.getDeclaredMethod("hashes"); 1694 m.setAccessible(true); 1695 Optional<Hasher.DependencyHashes> optHashes = 1696 (Optional<Hasher.DependencyHashes>) m.invoke(md); 1697 1698 if (optHashes.isPresent()) { 1699 Hasher.DependencyHashes hashes = optHashes.get(); 1700 sb.append("\nHashes:"); 1701 sb.append("\n Algorithm: " + hashes.algorithm()); 1702 hashes.names().stream().forEach(mod -> 1703 sb.append("\n ").append(mod) 1704 .append(": ").append(hashes.hashFor(mod))); 1705 } 1706 } catch (ReflectiveOperationException x) { 1707 throw new InternalError(x); 1708 } 1709 output(sb.toString()); 1710 } 1711 1712 private static String toBinaryName(String classname) { 1713 return (classname.replace('.', '/')) + ".class"; 1714 } 1715 1716 private boolean checkServices(byte[] moduleInfoBytes) 1717 throws IOException 1718 { 1719 ModuleDescriptor md; 1720 try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) { 1721 md = ModuleDescriptor.read(in); 1722 } 1723 Set<String> missing = md.provides() 1724 .values() 1725 .stream() 1726 .map(Provides::providers) 1727 .flatMap(Set::stream) 1728 .filter(p -> !jarEntries.contains(toBinaryName(p))) 1729 .collect(Collectors.toSet()); 1730 if (missing.size() > 0) { 1731 missing.stream().forEach(s -> fatalError(formatMsg("error.missing.provider", s))); 1732 return false; 1733 } 1734 return true; 1735 } 1736 1737 /** 1738 * Returns a byte array containing the module-info.class. 1739 * 1740 * If --module-version, --main-class, or other options were provided 1741 * then the corresponding class file attributes are added to the 1742 * module-info here. 1743 */ 1744 private byte[] addExtendedModuleAttributes(byte[] moduleInfoBytes) 1745 throws IOException 1746 { 1747 assert isModularJar(); 1748 1749 ModuleDescriptor md; 1750 try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) { 1751 md = ModuleDescriptor.read(in); 1752 } 1753 String name = md.name(); 1754 Set<ModuleDescriptor.Requires> dependences = md.requires(); 1755 Set<String> exported = md.exports() 1756 .stream() 1757 .map(ModuleDescriptor.Exports::source) 1758 .collect(Collectors.toSet()); 1759 1760 // copy the module-info.class into the jmod with the additional 1761 // attributes for the version, main class and other meta data 1762 try (InputStream in = new ByteArrayInputStream(moduleInfoBytes); 1763 ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 1764 ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in); 1765 1766 // Add (or replace) the ConcealedPackages attribute 1767 Set<String> conceals = packages.stream() 1768 .filter(p -> !exported.contains(p)) 1769 .collect(Collectors.toSet()); 1770 1771 extender.conceals(conceals); 1772 1773 // --main-class 1774 if (ename != null) 1775 extender.mainClass(ename); 1776 1777 // --module-version 1778 if (moduleVersion != null) 1779 extender.version(moduleVersion); 1780 1781 // --hash-dependencies 1782 if (dependenciesToHash != null) 1783 extender.hashes(hashDependences(name, dependences)); 1784 1785 extender.write(baos); 1786 return baos.toByteArray(); 1787 } 1788 } 1789 1790 /** 1791 * Examines the module dependences of the given module and computes the 1792 * hash of any module that matches the pattern {@code dependenciesToHash}. 1793 */ 1794 private Hasher.DependencyHashes 1795 hashDependences(String name, 1796 Set<ModuleDescriptor.Requires> moduleDependences) 1797 throws IOException 1798 { 1799 Map<String, Path> map = new HashMap<>(); 1800 Matcher matcher = dependenciesToHash.matcher(""); 1801 for (ModuleDescriptor.Requires md: moduleDependences) { 1802 String dn = md.name(); 1803 if (matcher.reset(dn).find()) { 1804 Optional<ModuleReference> omref = moduleFinder.find(dn); 1805 if (!omref.isPresent()) { 1806 throw new IOException(formatMsg2("error.hash.dep", name , dn)); 1807 } 1808 map.put(dn, modRefToPath(omref.get())); 1809 } 1810 } 1811 1812 if (map.size() == 0) { 1813 return null; 1814 } else { 1815 return Hasher.generate(map, "SHA-256"); 1816 } 1817 } 1818 1819 private static Path modRefToPath(ModuleReference mref) { 1820 URI location = mref.location().get(); 1821 return Paths.get(location); 1822 } 1823 }