1 /* 2 * Copyright (c) 2009, 2018, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.*; 25 import java.nio.charset.Charset; 26 import java.util.*; 27 import java.util.zip.*; 28 import java.text.MessageFormat; 29 30 /** 31 * A stripped-down version of Jar tool with a "-encoding" option to 32 * support non-UTF8 encoidng for entry name and comment. 33 */ 34 public class zip { 35 String program; 36 PrintStream out, err; 37 String fname; 38 String zname = ""; 39 String[] files; 40 Charset cs = Charset.forName("UTF-8"); 41 42 Map<String, File> entryMap = new HashMap<String, File>(); 43 Set<File> entries = new LinkedHashSet<File>(); 44 List<String> paths = new ArrayList<String>(); 45 46 CRC32 crc32 = new CRC32(); 47 /* 48 * cflag: create 49 * uflag: update 50 * xflag: xtract 51 * tflag: table 52 * vflag: verbose 53 * flag0: no zip compression (store only) 54 */ 55 boolean cflag, uflag, xflag, tflag, vflag, flag0; 56 57 private static ResourceBundle rsrc; 58 static { 59 try { 60 // just use the jar message 61 rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar"); 62 } catch (MissingResourceException e) { 63 throw new Error("Fatal: Resource for jar is missing"); 64 } 65 } 66 67 public zip(PrintStream out, PrintStream err, String program) { 68 this.out = out; 69 this.err = err; 70 this.program = program; 71 } 72 73 private boolean ok; 74 75 public synchronized boolean run(String args[]) { 76 ok = true; 77 if (!parseArgs(args)) { 78 return false; 79 } 80 try { 81 if (cflag || uflag) { 82 if (fname != null) { 83 zname = fname.replace(File.separatorChar, '/'); 84 if (zname.startsWith("./")) { 85 zname = zname.substring(2); 86 } 87 } 88 } 89 if (cflag) { 90 OutputStream out; 91 if (fname != null) { 92 out = new FileOutputStream(fname); 93 } else { 94 out = new FileOutputStream(FileDescriptor.out); 95 if (vflag) { 96 vflag = false; 97 } 98 } 99 expand(null, files, false); 100 create(new BufferedOutputStream(out, 4096)); 101 out.close(); 102 } else if (uflag) { 103 File inputFile = null, tmpFile = null; 104 FileInputStream in; 105 FileOutputStream out; 106 if (fname != null) { 107 inputFile = new File(fname); 108 String path = inputFile.getParent(); 109 tmpFile = File.createTempFile("tmp", null, 110 new File((path == null) ? "." : path)); 111 in = new FileInputStream(inputFile); 112 out = new FileOutputStream(tmpFile); 113 } else { 114 in = new FileInputStream(FileDescriptor.in); 115 out = new FileOutputStream(FileDescriptor.out); 116 vflag = false; 117 } 118 expand(null, files, true); 119 boolean updateOk = update(in, new BufferedOutputStream(out)); 120 if (ok) { 121 ok = updateOk; 122 } 123 in.close(); 124 out.close(); 125 if (fname != null) { 126 inputFile.delete(); 127 if (!tmpFile.renameTo(inputFile)) { 128 tmpFile.delete(); 129 throw new IOException(getMsg("error.write.file")); 130 } 131 tmpFile.delete(); 132 } 133 } else if (tflag) { 134 replaceFSC(files); 135 if (fname != null) { 136 list(fname, files); 137 } else { 138 InputStream in = new FileInputStream(FileDescriptor.in); 139 try{ 140 list(new BufferedInputStream(in), files); 141 } finally { 142 in.close(); 143 } 144 } 145 } else if (xflag) { 146 replaceFSC(files); 147 if (fname != null && files != null) { 148 extract(fname, files); 149 } else { 150 InputStream in = (fname == null) 151 ? new FileInputStream(FileDescriptor.in) 152 : new FileInputStream(fname); 153 try { 154 extract(new BufferedInputStream(in), files); 155 } finally { 156 in.close(); 157 } 158 } 159 } 160 } catch (IOException e) { 161 fatalError(e); 162 ok = false; 163 } catch (Error ee) { 164 ee.printStackTrace(); 165 ok = false; 166 } catch (Throwable t) { 167 t.printStackTrace(); 168 ok = false; 169 } 170 out.flush(); 171 err.flush(); 172 return ok; 173 } 174 175 176 boolean parseArgs(String args[]) { 177 try { 178 args = parse(args); 179 } catch (FileNotFoundException e) { 180 fatalError(formatMsg("error.cant.open", e.getMessage())); 181 return false; 182 } catch (IOException e) { 183 fatalError(e); 184 return false; 185 } 186 int count = 1; 187 try { 188 String flags = args[0]; 189 if (flags.startsWith("-")) { 190 flags = flags.substring(1); 191 } 192 for (int i = 0; i < flags.length(); i++) { 193 switch (flags.charAt(i)) { 194 case 'c': 195 if (xflag || tflag || uflag) { 196 usageError(); 197 return false; 198 } 199 cflag = true; 200 break; 201 case 'u': 202 if (cflag || xflag || tflag) { 203 usageError(); 204 return false; 205 } 206 uflag = true; 207 break; 208 case 'x': 209 if (cflag || uflag || tflag) { 210 usageError(); 211 return false; 212 } 213 xflag = true; 214 break; 215 case 't': 216 if (cflag || uflag || xflag) { 217 usageError(); 218 return false; 219 } 220 tflag = true; 221 break; 222 case 'v': 223 vflag = true; 224 break; 225 case 'f': 226 fname = args[count++]; 227 break; 228 case '0': 229 flag0 = true; 230 break; 231 default: 232 error(formatMsg("error.illegal.option", 233 String.valueOf(flags.charAt(i)))); 234 usageError(); 235 return false; 236 } 237 } 238 } catch (ArrayIndexOutOfBoundsException e) { 239 usageError(); 240 return false; 241 } 242 if (!cflag && !tflag && !xflag && !uflag) { 243 error(getMsg("error.bad.option")); 244 usageError(); 245 return false; 246 } 247 /* parse file arguments */ 248 int n = args.length - count; 249 if (n > 0) { 250 int k = 0; 251 String[] nameBuf = new String[n]; 252 try { 253 for (int i = count; i < args.length; i++) { 254 if (args[i].equals("-encoding")) { 255 cs = Charset.forName(args[++i]); 256 } else if (args[i].equals("-C")) { 257 /* change the directory */ 258 String dir = args[++i]; 259 dir = (dir.endsWith(File.separator) ? 260 dir : (dir + File.separator)); 261 dir = dir.replace(File.separatorChar, '/'); 262 while (dir.indexOf("//") > -1) { 263 dir = dir.replace("//", "/"); 264 } 265 paths.add(dir.replace(File.separatorChar, '/')); 266 nameBuf[k++] = dir + args[++i]; 267 } else { 268 nameBuf[k++] = args[i]; 269 } 270 } 271 } catch (ArrayIndexOutOfBoundsException e) { 272 e.printStackTrace(); 273 usageError(); 274 return false; 275 } 276 if (k != 0) { 277 files = new String[k]; 278 System.arraycopy(nameBuf, 0, files, 0, k); 279 } 280 } else if (cflag || uflag) { 281 error(getMsg("error.bad.uflag")); 282 usageError(); 283 return false; 284 } 285 return true; 286 } 287 288 void expand(File dir, String[] files, boolean isUpdate) { 289 if (files == null) { 290 return; 291 } 292 for (int i = 0; i < files.length; i++) { 293 File f; 294 if (dir == null) { 295 f = new File(files[i]); 296 } else { 297 f = new File(dir, files[i]); 298 } 299 if (f.isFile()) { 300 if (entries.add(f)) { 301 if (isUpdate) 302 entryMap.put(entryName(f.getPath()), f); 303 } 304 } else if (f.isDirectory()) { 305 if (entries.add(f)) { 306 if (isUpdate) { 307 String dirPath = f.getPath(); 308 dirPath = (dirPath.endsWith(File.separator)) ? dirPath : 309 (dirPath + File.separator); 310 entryMap.put(entryName(dirPath), f); 311 } 312 expand(f, f.list(), isUpdate); 313 } 314 } else { 315 error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); 316 ok = false; 317 } 318 } 319 } 320 321 void create(OutputStream out) throws IOException 322 { 323 try (ZipOutputStream zos = new ZipOutputStream(out, cs)) { 324 if (flag0) { 325 zos.setMethod(ZipOutputStream.STORED); 326 } 327 for (File file: entries) { 328 addFile(zos, file); 329 } 330 } 331 } 332 333 boolean update(InputStream in, OutputStream out) throws IOException 334 { 335 try (ZipInputStream zis = new ZipInputStream(in, cs); 336 ZipOutputStream zos = new ZipOutputStream(out, cs)) 337 { 338 ZipEntry e = null; 339 byte[] buf = new byte[1024]; 340 int n = 0; 341 boolean updateOk = true; 342 343 // put the old entries first, replace if necessary 344 while ((e = zis.getNextEntry()) != null) { 345 String name = e.getName(); 346 if (!entryMap.containsKey(name)) { // copy the old stuff 347 // do our own compression 348 ZipEntry e2 = new ZipEntry(name); 349 e2.setMethod(e.getMethod()); 350 e2.setTime(e.getTime()); 351 e2.setComment(e.getComment()); 352 e2.setExtra(e.getExtra()); 353 if (e.getMethod() == ZipEntry.STORED) { 354 e2.setSize(e.getSize()); 355 e2.setCrc(e.getCrc()); 356 } 357 zos.putNextEntry(e2); 358 while ((n = zis.read(buf, 0, buf.length)) != -1) { 359 zos.write(buf, 0, n); 360 } 361 } else { // replace with the new files 362 File f = entryMap.get(name); 363 addFile(zos, f); 364 entryMap.remove(name); 365 entries.remove(f); 366 } 367 } 368 369 // add the remaining new files 370 for (File f: entries) { 371 addFile(zos, f); 372 } 373 } 374 return updateOk; 375 } 376 377 private String entryName(String name) { 378 name = name.replace(File.separatorChar, '/'); 379 String matchPath = ""; 380 for (String path : paths) { 381 if (name.startsWith(path) && (path.length() > matchPath.length())) { 382 matchPath = path; 383 } 384 } 385 name = name.substring(matchPath.length()); 386 387 if (name.startsWith("/")) { 388 name = name.substring(1); 389 } else if (name.startsWith("./")) { 390 name = name.substring(2); 391 } 392 return name; 393 } 394 395 void addFile(ZipOutputStream zos, File file) throws IOException { 396 String name = file.getPath(); 397 boolean isDir = file.isDirectory(); 398 if (isDir) { 399 name = name.endsWith(File.separator) ? name : 400 (name + File.separator); 401 } 402 name = entryName(name); 403 404 if (name.equals("") || name.equals(".") || name.equals(zname)) { 405 return; 406 } 407 408 long size = isDir ? 0 : file.length(); 409 410 if (vflag) { 411 out.print(formatMsg("out.adding", name)); 412 } 413 ZipEntry e = new ZipEntry(name); 414 e.setTime(file.lastModified()); 415 if (size == 0) { 416 e.setMethod(ZipEntry.STORED); 417 e.setSize(0); 418 e.setCrc(0); 419 } else if (flag0) { 420 e.setSize(size); 421 e.setMethod(ZipEntry.STORED); 422 crc32File(e, file); 423 } 424 zos.putNextEntry(e); 425 if (!isDir) { 426 byte[] buf = new byte[8192]; 427 int len; 428 InputStream is = new BufferedInputStream(new FileInputStream(file)); 429 while ((len = is.read(buf, 0, buf.length)) != -1) { 430 zos.write(buf, 0, len); 431 } 432 is.close(); 433 } 434 zos.closeEntry(); 435 /* report how much compression occurred. */ 436 if (vflag) { 437 size = e.getSize(); 438 long csize = e.getCompressedSize(); 439 out.print(formatMsg2("out.size", String.valueOf(size), 440 String.valueOf(csize))); 441 if (e.getMethod() == ZipEntry.DEFLATED) { 442 long ratio = 0; 443 if (size != 0) { 444 ratio = ((size - csize) * 100) / size; 445 } 446 output(formatMsg("out.deflated", String.valueOf(ratio))); 447 } else { 448 output(getMsg("out.stored")); 449 } 450 } 451 } 452 453 private void crc32File(ZipEntry e, File f) throws IOException { 454 InputStream is = new BufferedInputStream(new FileInputStream(f)); 455 byte[] buf = new byte[8192]; 456 crc32.reset(); 457 int r = 0; 458 int nread = 0; 459 long len = f.length(); 460 while ((r = is.read(buf)) != -1) { 461 nread += r; 462 crc32.update(buf, 0, r); 463 } 464 is.close(); 465 if (nread != (int) len) { 466 throw new ZipException(formatMsg( 467 "error.incorrect.length", f.getPath())); 468 } 469 e.setCrc(crc32.getValue()); 470 } 471 472 void replaceFSC(String files[]) { 473 if (files != null) { 474 for (String file : files) { 475 file = file.replace(File.separatorChar, '/'); 476 } 477 } 478 } 479 480 Set<ZipEntry> newDirSet() { 481 return new HashSet<ZipEntry>() { 482 public boolean add(ZipEntry e) { 483 return (e == null || super.add(e)); 484 }}; 485 } 486 487 void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException { 488 for (ZipEntry ze : zes) { 489 long lastModified = ze.getTime(); 490 if (lastModified != -1) { 491 File f = new File(ze.getName().replace('/', File.separatorChar)); 492 f.setLastModified(lastModified); 493 } 494 } 495 } 496 497 void extract(InputStream in, String files[]) throws IOException { 498 ZipInputStream zis = new ZipInputStream(in, cs); 499 ZipEntry e; 500 Set<ZipEntry> dirs = newDirSet(); 501 while ((e = zis.getNextEntry()) != null) { 502 if (files == null) { 503 dirs.add(extractFile(zis, e)); 504 } else { 505 String name = e.getName(); 506 for (String file : files) { 507 if (name.startsWith(file)) { 508 dirs.add(extractFile(zis, e)); 509 break; 510 } 511 } 512 } 513 } 514 updateLastModifiedTime(dirs); 515 } 516 517 void extract(String fname, String files[]) throws IOException { 518 try (ZipFile zf = new ZipFile(fname, cs)) { 519 Set<ZipEntry> dirs = newDirSet(); 520 Enumeration<? extends ZipEntry> zes = zf.entries(); 521 while (zes.hasMoreElements()) { 522 ZipEntry e = zes.nextElement(); 523 InputStream is; 524 if (files == null) { 525 dirs.add(extractFile(zf.getInputStream(e), e)); 526 } else { 527 String name = e.getName(); 528 for (String file : files) { 529 if (name.startsWith(file)) { 530 dirs.add(extractFile(zf.getInputStream(e), e)); 531 break; 532 } 533 } 534 } 535 } 536 } 537 updateLastModifiedTime(dirs); 538 } 539 540 ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException { 541 ZipEntry rc = null; 542 String name = e.getName(); 543 File f = new File(e.getName().replace('/', File.separatorChar)); 544 if (e.isDirectory()) { 545 if (f.exists()) { 546 if (!f.isDirectory()) { 547 throw new IOException(formatMsg("error.create.dir", 548 f.getPath())); 549 } 550 } else { 551 if (!f.mkdirs()) { 552 throw new IOException(formatMsg("error.create.dir", 553 f.getPath())); 554 } else { 555 rc = e; 556 } 557 } 558 if (vflag) { 559 output(formatMsg("out.create", name)); 560 } 561 } else { 562 if (f.getParent() != null) { 563 File d = new File(f.getParent()); 564 if (!d.exists() && !d.mkdirs() || !d.isDirectory()) { 565 throw new IOException(formatMsg( 566 "error.create.dir", d.getPath())); 567 } 568 } 569 OutputStream os = new FileOutputStream(f); 570 byte[] b = new byte[8192]; 571 int len; 572 try { 573 while ((len = is.read(b, 0, b.length)) != -1) { 574 os.write(b, 0, len); 575 } 576 } finally { 577 if (is instanceof ZipInputStream) 578 ((ZipInputStream)is).closeEntry(); 579 else 580 is.close(); 581 os.close(); 582 } 583 if (vflag) { 584 if (e.getMethod() == ZipEntry.DEFLATED) { 585 output(formatMsg("out.inflated", name)); 586 } else { 587 output(formatMsg("out.extracted", name)); 588 } 589 } 590 } 591 long lastModified = e.getTime(); 592 if (lastModified != -1) { 593 f.setLastModified(lastModified); 594 } 595 return rc; 596 } 597 598 void list(InputStream in, String files[]) throws IOException { 599 ZipInputStream zis = new ZipInputStream(in, cs); 600 ZipEntry e; 601 while ((e = zis.getNextEntry()) != null) { 602 zis.closeEntry(); 603 printEntry(e, files); 604 } 605 } 606 607 void list(String fname, String files[]) throws IOException { 608 try (ZipFile zf = new ZipFile(fname, cs)) { 609 Enumeration<? extends ZipEntry> zes = zf.entries(); 610 while (zes.hasMoreElements()) { 611 printEntry(zes.nextElement(), files); 612 } 613 } 614 } 615 616 void printEntry(ZipEntry e, String[] files) throws IOException { 617 if (files == null) { 618 printEntry(e); 619 } else { 620 String name = e.getName(); 621 for (String file : files) { 622 if (name.startsWith(file)) { 623 printEntry(e); 624 return; 625 } 626 } 627 } 628 } 629 630 void printEntry(ZipEntry e) throws IOException { 631 if (vflag) { 632 StringBuilder sb = new StringBuilder(); 633 String s = Long.toString(e.getSize()); 634 for (int i = 6 - s.length(); i > 0; --i) { 635 sb.append(' '); 636 } 637 sb.append(s).append(' ').append(new Date(e.getTime()).toString()); 638 sb.append(' ').append(e.getName()); 639 output(sb.toString()); 640 } else { 641 output(e.getName()); 642 } 643 } 644 645 void usageError() { 646 error( 647 "Usage: zip {ctxu}[vf0] [zip-file] [-encoding encname][-C dir] files ...\n" + 648 "Options:\n" + 649 " -c create new archive\n" + 650 " -t list table of contents for archive\n" + 651 " -x extract named (or all) files from archive\n" + 652 " -u update existing archive\n" + 653 " -v generate verbose output on standard output\n" + 654 " -f specify archive file name\n" + 655 " -0 store only; use no ZIP compression\n" + 656 " -C change to the specified directory and include the following file\n" + 657 "If any file is a directory then it is processed recursively.\n"); 658 } 659 660 void fatalError(Exception e) { 661 e.printStackTrace(); 662 } 663 664 665 void fatalError(String s) { 666 error(program + ": " + s); 667 } 668 669 670 protected void output(String s) { 671 out.println(s); 672 } 673 674 protected void error(String s) { 675 err.println(s); 676 } 677 678 private String getMsg(String key) { 679 try { 680 return (rsrc.getString(key)); 681 } catch (MissingResourceException e) { 682 throw new Error("Error in message file"); 683 } 684 } 685 686 private String formatMsg(String key, String arg) { 687 String msg = getMsg(key); 688 String[] args = new String[1]; 689 args[0] = arg; 690 return MessageFormat.format(msg, (Object[]) args); 691 } 692 693 private String formatMsg2(String key, String arg, String arg1) { 694 String msg = getMsg(key); 695 String[] args = new String[2]; 696 args[0] = arg; 697 args[1] = arg1; 698 return MessageFormat.format(msg, (Object[]) args); 699 } 700 701 public static String[] parse(String[] args) throws IOException 702 { 703 ArrayList<String> newArgs = new ArrayList<String>(args.length); 704 for (int i = 0; i < args.length; i++) { 705 String arg = args[i]; 706 if (arg.length() > 1 && arg.charAt(0) == '@') { 707 arg = arg.substring(1); 708 if (arg.charAt(0) == '@') { 709 newArgs.add(arg); 710 } else { 711 loadCmdFile(arg, newArgs); 712 } 713 } else { 714 newArgs.add(arg); 715 } 716 } 717 return newArgs.toArray(new String[newArgs.size()]); 718 } 719 720 private static void loadCmdFile(String name, List<String> args) throws IOException 721 { 722 Reader r = new BufferedReader(new FileReader(name)); 723 StreamTokenizer st = new StreamTokenizer(r); 724 st.resetSyntax(); 725 st.wordChars(' ', 255); 726 st.whitespaceChars(0, ' '); 727 st.commentChar('#'); 728 st.quoteChar('"'); 729 st.quoteChar('\''); 730 while (st.nextToken() != st.TT_EOF) { 731 args.add(st.sval); 732 } 733 r.close(); 734 } 735 736 public static void main(String args[]) { 737 zip z = new zip(System.out, System.err, "zip"); 738 System.exit(z.run(args) ? 0 : 1); 739 } 740 } 741