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