/* * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.*; import java.nio.charset.Charset; import java.util.*; import java.util.zip.*; import java.text.MessageFormat; /** * A stripped-down version of Jar tool with a "-encoding" option to * support non-UTF8 encoidng for entry name and comment. */ public class zip { String program; PrintStream out, err; String fname; String zname = ""; String[] files; Charset cs = Charset.forName("UTF-8"); Map<String, File> entryMap = new HashMap<String, File>(); Set<File> entries = new LinkedHashSet<File>(); List<String> paths = new ArrayList<String>(); CRC32 crc32 = new CRC32(); /* * cflag: create * uflag: update * xflag: xtract * tflag: table * vflag: verbose * flag0: no zip compression (store only) */ boolean cflag, uflag, xflag, tflag, vflag, flag0; private static ResourceBundle rsrc; static { try { // just use the jar message rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar"); } catch (MissingResourceException e) { throw new Error("Fatal: Resource for jar is missing"); } } public zip(PrintStream out, PrintStream err, String program) { this.out = out; this.err = err; this.program = program; } private boolean ok; public synchronized boolean run(String args[]) { ok = true; if (!parseArgs(args)) { return false; } try { if (cflag || uflag) { if (fname != null) { zname = fname.replace(File.separatorChar, '/'); if (zname.startsWith("./")) { zname = zname.substring(2); } } } if (cflag) { OutputStream out; if (fname != null) { out = new FileOutputStream(fname); } else { out = new FileOutputStream(FileDescriptor.out); if (vflag) { vflag = false; } } expand(null, files, false); create(new BufferedOutputStream(out, 4096)); out.close(); } else if (uflag) { File inputFile = null, tmpFile = null; FileInputStream in; FileOutputStream out; if (fname != null) { inputFile = new File(fname); String path = inputFile.getParent(); tmpFile = File.createTempFile("tmp", null, new File((path == null) ? "." : path)); in = new FileInputStream(inputFile); out = new FileOutputStream(tmpFile); } else { in = new FileInputStream(FileDescriptor.in); out = new FileOutputStream(FileDescriptor.out); vflag = false; } expand(null, files, true); boolean updateOk = update(in, new BufferedOutputStream(out)); if (ok) { ok = updateOk; } in.close(); out.close(); if (fname != null) { inputFile.delete(); if (!tmpFile.renameTo(inputFile)) { tmpFile.delete(); throw new IOException(getMsg("error.write.file")); } tmpFile.delete(); } } else if (tflag) { replaceFSC(files); if (fname != null) { list(fname, files); } else { InputStream in = new FileInputStream(FileDescriptor.in); try{ list(new BufferedInputStream(in), files); } finally { in.close(); } } } else if (xflag) { replaceFSC(files); if (fname != null && files != null) { extract(fname, files); } else { InputStream in = (fname == null) ? new FileInputStream(FileDescriptor.in) : new FileInputStream(fname); try { extract(new BufferedInputStream(in), files); } finally { in.close(); } } } } catch (IOException e) { fatalError(e); ok = false; } catch (Error ee) { ee.printStackTrace(); ok = false; } catch (Throwable t) { t.printStackTrace(); ok = false; } out.flush(); err.flush(); return ok; } boolean parseArgs(String args[]) { try { args = parse(args); } catch (FileNotFoundException e) { fatalError(formatMsg("error.cant.open", e.getMessage())); return false; } catch (IOException e) { fatalError(e); return false; } int count = 1; try { String flags = args[0]; if (flags.startsWith("-")) { flags = flags.substring(1); } for (int i = 0; i < flags.length(); i++) { switch (flags.charAt(i)) { case 'c': if (xflag || tflag || uflag) { usageError(); return false; } cflag = true; break; case 'u': if (cflag || xflag || tflag) { usageError(); return false; } uflag = true; break; case 'x': if (cflag || uflag || tflag) { usageError(); return false; } xflag = true; break; case 't': if (cflag || uflag || xflag) { usageError(); return false; } tflag = true; break; case 'v': vflag = true; break; case 'f': fname = args[count++]; break; case '0': flag0 = true; break; default: error(formatMsg("error.illegal.option", String.valueOf(flags.charAt(i)))); usageError(); return false; } } } catch (ArrayIndexOutOfBoundsException e) { usageError(); return false; } if (!cflag && !tflag && !xflag && !uflag) { error(getMsg("error.bad.option")); usageError(); return false; } /* parse file arguments */ int n = args.length - count; if (n > 0) { int k = 0; String[] nameBuf = new String[n]; try { for (int i = count; i < args.length; i++) { if (args[i].equals("-encoding")) { cs = Charset.forName(args[++i]); } else if (args[i].equals("-C")) { /* change the directory */ String dir = args[++i]; dir = (dir.endsWith(File.separator) ? dir : (dir + File.separator)); dir = dir.replace(File.separatorChar, '/'); while (dir.indexOf("//") > -1) { dir = dir.replace("//", "/"); } paths.add(dir.replace(File.separatorChar, '/')); nameBuf[k++] = dir + args[++i]; } else { nameBuf[k++] = args[i]; } } } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); usageError(); return false; } if (k != 0) { files = new String[k]; System.arraycopy(nameBuf, 0, files, 0, k); } } else if (cflag || uflag) { error(getMsg("error.bad.uflag")); usageError(); return false; } return true; } void expand(File dir, String[] files, boolean isUpdate) { if (files == null) { return; } for (int i = 0; i < files.length; i++) { File f; if (dir == null) { f = new File(files[i]); } else { f = new File(dir, files[i]); } if (f.isFile()) { if (entries.add(f)) { if (isUpdate) entryMap.put(entryName(f.getPath()), f); } } else if (f.isDirectory()) { if (entries.add(f)) { if (isUpdate) { String dirPath = f.getPath(); dirPath = (dirPath.endsWith(File.separator)) ? dirPath : (dirPath + File.separator); entryMap.put(entryName(dirPath), f); } expand(f, f.list(), isUpdate); } } else { error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); ok = false; } } } void create(OutputStream out) throws IOException { try (ZipOutputStream zos = new ZipOutputStream(out, cs)) { if (flag0) { zos.setMethod(ZipOutputStream.STORED); } for (File file: entries) { addFile(zos, file); } } } boolean update(InputStream in, OutputStream out) throws IOException { try (ZipInputStream zis = new ZipInputStream(in, cs); ZipOutputStream zos = new ZipOutputStream(out, cs)) { ZipEntry e = null; byte[] buf = new byte[1024]; int n = 0; boolean updateOk = true; // put the old entries first, replace if necessary while ((e = zis.getNextEntry()) != null) { String name = e.getName(); if (!entryMap.containsKey(name)) { // copy the old stuff // do our own compression ZipEntry e2 = new ZipEntry(name); e2.setMethod(e.getMethod()); e2.setTime(e.getTime()); e2.setComment(e.getComment()); e2.setExtra(e.getExtra()); if (e.getMethod() == ZipEntry.STORED) { e2.setSize(e.getSize()); e2.setCrc(e.getCrc()); } zos.putNextEntry(e2); while ((n = zis.read(buf, 0, buf.length)) != -1) { zos.write(buf, 0, n); } } else { // replace with the new files File f = entryMap.get(name); addFile(zos, f); entryMap.remove(name); entries.remove(f); } } // add the remaining new files for (File f: entries) { addFile(zos, f); } } return updateOk; } private String entryName(String name) { name = name.replace(File.separatorChar, '/'); String matchPath = ""; for (String path : paths) { if (name.startsWith(path) && (path.length() > matchPath.length())) { matchPath = path; } } name = name.substring(matchPath.length()); if (name.startsWith("/")) { name = name.substring(1); } else if (name.startsWith("./")) { name = name.substring(2); } return name; } void addFile(ZipOutputStream zos, File file) throws IOException { String name = file.getPath(); boolean isDir = file.isDirectory(); if (isDir) { name = name.endsWith(File.separator) ? name : (name + File.separator); } name = entryName(name); if (name.equals("") || name.equals(".") || name.equals(zname)) { return; } long size = isDir ? 0 : file.length(); if (vflag) { out.print(formatMsg("out.adding", name)); } ZipEntry e = new ZipEntry(name); e.setTime(file.lastModified()); if (size == 0) { e.setMethod(ZipEntry.STORED); e.setSize(0); e.setCrc(0); } else if (flag0) { e.setSize(size); e.setMethod(ZipEntry.STORED); crc32File(e, file); } zos.putNextEntry(e); if (!isDir) { byte[] buf = new byte[8192]; int len; InputStream is = new BufferedInputStream(new FileInputStream(file)); while ((len = is.read(buf, 0, buf.length)) != -1) { zos.write(buf, 0, len); } is.close(); } zos.closeEntry(); /* report how much compression occurred. */ if (vflag) { size = e.getSize(); long csize = e.getCompressedSize(); out.print(formatMsg2("out.size", String.valueOf(size), String.valueOf(csize))); if (e.getMethod() == ZipEntry.DEFLATED) { long ratio = 0; if (size != 0) { ratio = ((size - csize) * 100) / size; } output(formatMsg("out.deflated", String.valueOf(ratio))); } else { output(getMsg("out.stored")); } } } private void crc32File(ZipEntry e, File f) throws IOException { InputStream is = new BufferedInputStream(new FileInputStream(f)); byte[] buf = new byte[8192]; crc32.reset(); int r = 0; int nread = 0; long len = f.length(); while ((r = is.read(buf)) != -1) { nread += r; crc32.update(buf, 0, r); } is.close(); if (nread != (int) len) { throw new ZipException(formatMsg( "error.incorrect.length", f.getPath())); } e.setCrc(crc32.getValue()); } void replaceFSC(String files[]) { if (files != null) { for (String file : files) { file = file.replace(File.separatorChar, '/'); } } } Set<ZipEntry> newDirSet() { return new HashSet<ZipEntry>() { public boolean add(ZipEntry e) { return (e == null || super.add(e)); }}; } void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException { for (ZipEntry ze : zes) { long lastModified = ze.getTime(); if (lastModified != -1) { File f = new File(ze.getName().replace('/', File.separatorChar)); f.setLastModified(lastModified); } } } void extract(InputStream in, String files[]) throws IOException { ZipInputStream zis = new ZipInputStream(in, cs); ZipEntry e; Set<ZipEntry> dirs = newDirSet(); while ((e = zis.getNextEntry()) != null) { if (files == null) { dirs.add(extractFile(zis, e)); } else { String name = e.getName(); for (String file : files) { if (name.startsWith(file)) { dirs.add(extractFile(zis, e)); break; } } } } updateLastModifiedTime(dirs); } void extract(String fname, String files[]) throws IOException { try (ZipFile zf = new ZipFile(fname, cs)) { Set<ZipEntry> dirs = newDirSet(); Enumeration<? extends ZipEntry> zes = zf.entries(); while (zes.hasMoreElements()) { ZipEntry e = zes.nextElement(); InputStream is; if (files == null) { dirs.add(extractFile(zf.getInputStream(e), e)); } else { String name = e.getName(); for (String file : files) { if (name.startsWith(file)) { dirs.add(extractFile(zf.getInputStream(e), e)); break; } } } } } updateLastModifiedTime(dirs); } ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException { ZipEntry rc = null; String name = e.getName(); File f = new File(e.getName().replace('/', File.separatorChar)); if (e.isDirectory()) { if (f.exists()) { if (!f.isDirectory()) { throw new IOException(formatMsg("error.create.dir", f.getPath())); } } else { if (!f.mkdirs()) { throw new IOException(formatMsg("error.create.dir", f.getPath())); } else { rc = e; } } if (vflag) { output(formatMsg("out.create", name)); } } else { if (f.getParent() != null) { File d = new File(f.getParent()); if (!d.exists() && !d.mkdirs() || !d.isDirectory()) { throw new IOException(formatMsg( "error.create.dir", d.getPath())); } } OutputStream os = new FileOutputStream(f); byte[] b = new byte[8192]; int len; try { while ((len = is.read(b, 0, b.length)) != -1) { os.write(b, 0, len); } } finally { if (is instanceof ZipInputStream) ((ZipInputStream)is).closeEntry(); else is.close(); os.close(); } if (vflag) { if (e.getMethod() == ZipEntry.DEFLATED) { output(formatMsg("out.inflated", name)); } else { output(formatMsg("out.extracted", name)); } } } long lastModified = e.getTime(); if (lastModified != -1) { f.setLastModified(lastModified); } return rc; } void list(InputStream in, String files[]) throws IOException { ZipInputStream zis = new ZipInputStream(in, cs); ZipEntry e; while ((e = zis.getNextEntry()) != null) { zis.closeEntry(); printEntry(e, files); } } void list(String fname, String files[]) throws IOException { try (ZipFile zf = new ZipFile(fname, cs)) { Enumeration<? extends ZipEntry> zes = zf.entries(); while (zes.hasMoreElements()) { printEntry(zes.nextElement(), files); } } } void printEntry(ZipEntry e, String[] files) throws IOException { if (files == null) { printEntry(e); } else { String name = e.getName(); for (String file : files) { if (name.startsWith(file)) { printEntry(e); return; } } } } void printEntry(ZipEntry e) throws IOException { if (vflag) { StringBuilder sb = new StringBuilder(); String s = Long.toString(e.getSize()); for (int i = 6 - s.length(); i > 0; --i) { sb.append(' '); } sb.append(s).append(' ').append(new Date(e.getTime()).toString()); sb.append(' ').append(e.getName()); output(sb.toString()); } else { output(e.getName()); } } void usageError() { error( "Usage: zip {ctxu}[vf0] [zip-file] [-encoding encname][-C dir] files ...\n" + "Options:\n" + " -c create new archive\n" + " -t list table of contents for archive\n" + " -x extract named (or all) files from archive\n" + " -u update existing archive\n" + " -v generate verbose output on standard output\n" + " -f specify archive file name\n" + " -0 store only; use no ZIP compression\n" + " -C change to the specified directory and include the following file\n" + "If any file is a directory then it is processed recursively.\n"); } void fatalError(Exception e) { e.printStackTrace(); } void fatalError(String s) { error(program + ": " + s); } protected void output(String s) { out.println(s); } protected void error(String s) { err.println(s); } private String getMsg(String key) { try { return (rsrc.getString(key)); } catch (MissingResourceException e) { throw new Error("Error in message file"); } } private String formatMsg(String key, String arg) { String msg = getMsg(key); String[] args = new String[1]; args[0] = arg; return MessageFormat.format(msg, (Object[]) args); } private String formatMsg2(String key, String arg, String arg1) { String msg = getMsg(key); String[] args = new String[2]; args[0] = arg; args[1] = arg1; return MessageFormat.format(msg, (Object[]) args); } public static String[] parse(String[] args) throws IOException { ArrayList<String> newArgs = new ArrayList<String>(args.length); for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.length() > 1 && arg.charAt(0) == '@') { arg = arg.substring(1); if (arg.charAt(0) == '@') { newArgs.add(arg); } else { loadCmdFile(arg, newArgs); } } else { newArgs.add(arg); } } return newArgs.toArray(new String[newArgs.size()]); } private static void loadCmdFile(String name, List<String> args) throws IOException { Reader r = new BufferedReader(new FileReader(name)); StreamTokenizer st = new StreamTokenizer(r); st.resetSyntax(); st.wordChars(' ', 255); st.whitespaceChars(0, ' '); st.commentChar('#'); st.quoteChar('"'); st.quoteChar('\''); while (st.nextToken() != st.TT_EOF) { args.add(st.sval); } r.close(); } public static void main(String args[]) { zip z = new zip(System.out, System.err, "zip"); System.exit(z.run(args) ? 0 : 1); } }