blob: 62fb71398a7a11587b9c125d5824f815c4a05aa2 [file] [log] [blame]
sherman52c91932009-04-16 21:00:42 -07001/*
ohair2283b9d2010-05-25 15:58:33 -07002 * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
sherman52c91932009-04-16 21:00:42 -07003 * 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
ohair2283b9d2010-05-25 15:58:33 -07007 * published by the Free Software Foundation. Oracle designates this
sherman52c91932009-04-16 21:00:42 -07008 * particular file as subject to the "Classpath" exception as provided
ohair2283b9d2010-05-25 15:58:33 -07009 * by Oracle in the LICENSE file that accompanied this code.
sherman52c91932009-04-16 21:00:42 -070010 *
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 *
ohair2283b9d2010-05-25 15:58:33 -070021 * 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.
sherman52c91932009-04-16 21:00:42 -070024 */
25
26import java.io.*;
27import java.nio.charset.Charset;
28import java.util.*;
29import java.util.zip.*;
30import 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 */
36public 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 {
smarkse4682702011-02-25 02:06:10 -0800325 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 }
sherman52c91932009-04-16 21:00:42 -0700332 }
sherman52c91932009-04-16 21:00:42 -0700333 }
334
335 boolean update(InputStream in, OutputStream out) throws IOException
336 {
smarkse4682702011-02-25 02:06:10 -0800337 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;
sherman52c91932009-04-16 21:00:42 -0700344
smarkse4682702011-02-25 02:06:10 -0800345 // 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);
sherman52c91932009-04-16 21:00:42 -0700368 }
smarkse4682702011-02-25 02:06:10 -0800369 }
370
371 // add the remaining new files
372 for (File f: entries) {
sherman52c91932009-04-16 21:00:42 -0700373 addFile(zos, f);
sherman52c91932009-04-16 21:00:42 -0700374 }
375 }
sherman52c91932009-04-16 21:00:42 -0700376 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 {
smarkse4682702011-02-25 02:06:10 -0800520 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 }
sherman52c91932009-04-16 21:00:42 -0700535 }
536 }
537 }
538 }
sherman52c91932009-04-16 21:00:42 -0700539 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 {
smarkse4682702011-02-25 02:06:10 -0800610 try (ZipFile zf = new ZipFile(fname, cs)) {
611 Enumeration<? extends ZipEntry> zes = zf.entries();
612 while (zes.hasMoreElements()) {
613 printEntry(zes.nextElement(), files);
614 }
sherman52c91932009-04-16 21:00:42 -0700615 }
sherman52c91932009-04-16 21:00:42 -0700616 }
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