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