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