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