blob: 0ac91c5c130d0045751a4d314ea031596199898f [file] [log] [blame]
/*
* Copyright (c) 2009, 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
{
ZipOutputStream zos = new ZipOutputStream(out, cs);
if (flag0) {
zos.setMethod(ZipOutputStream.STORED);
}
for (File file: entries) {
addFile(zos, file);
}
zos.close();
}
boolean update(InputStream in, OutputStream out) throws IOException
{
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);
}
zis.close();
zos.close();
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 {
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;
}
}
}
}
zf.close();
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 {
ZipFile zf = new ZipFile(fname, cs);
Enumeration<? extends ZipEntry> zes = zf.entries();
while (zes.hasMoreElements()) {
printEntry(zes.nextElement(), files);
}
zf.close();
}
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);
}
}