001: // Copyright (c) 1997 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.bytecode;
005:
006: import java.util.Hashtable;
007: import java.io.*;
008: import java.util.zip.*;
009:
010: /** A class to manipulate a .zip archive.
011: * Does not handle compression/uncompression, though that could be added.
012: * When used an an application. provides a simplified tar-like interface.
013: * @author Per Bothner <bothner@cygnus.com>
014: */
015:
016: public class ZipArchive {
017: //Hashtable directory;
018:
019: static final int LREC_SIZE = 26; // lengths of local file headers, central
020: static final int CREC_SIZE = 42; // directory headers, and the end-of-
021: static final int ECREC_SIZE = 18; // central-dir record, respectively
022:
023: /* Field offsets of end-of-central directory. */
024: static final int TOTAL_ENTRIES_CENTRAL_DIR = 10;
025: static final int SIZE_CENTRAL_DIRECTORY = 12;
026:
027: /* Field offsets of central directory. */
028: static final int C_COMPRESSED_SIZE = 16;
029: static final int C_UNCOMPRESSED_SIZE = 20;
030: static final int C_FILENAME_LENGTH = 24;
031: static final int C_RELATIVE_OFFSET_LOCAL_HEADER = 38;
032:
033: RandomAccessFile file;
034: String archive_name;
035:
036: public ZipArchive(String name, String mode) throws IOException {
037: archive_name = name;
038: file = new RandomAccessFile(name, mode);
039: readDirectory();
040: }
041:
042: public ZipArchive(File file, String mode) throws IOException {
043: archive_name = file.getName();
044: this .file = new RandomAccessFile(file, mode);
045: readDirectory();
046: }
047:
048: /** Return a directory entry with the given name, or null if not found. */
049: public ZipMember find(String name) {
050: for (ZipMember zipd = firstEntry; zipd != null; zipd = zipd.next) {
051: if (zipd.matches(name))
052: return zipd;
053: }
054: return null;
055: }
056:
057: long size;
058: int count_in; // Number of entries read
059: int count_out;
060:
061: public int size() {
062: return count_in + count_out;
063: }
064:
065: ZipMember firstEntry, lastEntry;
066:
067: byte[] buffer = new byte[100];
068:
069: void write2(int val, byte[] buffer, int pos) {
070: buffer[pos] = (byte) val;
071: buffer[pos + 1] = (byte) (val >> 8);
072: }
073:
074: void write4(int val, byte[] buffer, int pos) {
075: buffer[pos] = (byte) (val);
076: buffer[pos + 1] = (byte) (val >> 8);
077: buffer[pos + 2] = (byte) (val >> 16);
078: buffer[pos + 3] = (byte) (val >> 24);
079: }
080:
081: void writeLocalHeader(ZipMember zmember) throws IOException {
082: for (int i = 4 + LREC_SIZE; --i >= 0;)
083: buffer[i] = 0;
084: buffer[0] = (byte) 'P';
085: buffer[1] = (byte) 'K';
086: buffer[2] = (byte) '\003';
087: buffer[3] = (byte) '\004';
088: buffer[4] = (byte) '\n'; // 4+L_VERSION_NEEDED_TO_EXTRACT_0
089: write2(zmember.getName().length(), buffer, 4 + 22);
090: write4((int) zmember.compressed_size, buffer, 4 + 14);
091: write4((int) zmember.getSize(), buffer, 4 + 18);
092: file.write(buffer, 0, 4 + LREC_SIZE);
093: }
094:
095: void writeCentralHeader(ZipMember zmember) throws IOException {
096: for (int i = 4 + CREC_SIZE; --i >= 0;)
097: buffer[i] = 0;
098: buffer[0] = (byte) 'P';
099: buffer[1] = (byte) 'K';
100: buffer[2] = (byte) '\001';
101: buffer[3] = (byte) '\002';
102: buffer[4] = (byte) '\024'; // 4+C_VERSION_MADE_BY_0
103: buffer[5] = (byte) '\003'; // 4+C_VERSION_MADE_BY_1
104: buffer[6] = (byte) '\n'; // 4+C_VERSION_NEEDED_TO_EXTRACT_0
105: write4((int) zmember.compressed_size, buffer, 4 + 16);
106: write4((int) zmember.getSize(), buffer, 4 + 20);
107: write2(zmember.getName().length(), buffer, 4 + 24);
108: write4((int) zmember.relative_offset_local_header, buffer,
109: 4 + 38);
110: file.write(buffer, 0, 4 + CREC_SIZE);
111: }
112:
113: public void close() throws IOException {
114: if (count_out > 0)
115: writeEndHeaders();
116: file.close();
117: }
118:
119: void writeEndHeaders() throws IOException {
120: int count = 0;
121: long dir_start = lastEntry == null ? 0 : lastEntry.fileStart()
122: + lastEntry.compressed_size;
123: file.seek(dir_start);
124: for (ZipMember zmemb = firstEntry; zmemb != null; zmemb = zmemb.next) {
125: writeCentralHeader(zmemb);
126: String name = zmemb.getName();
127: int len = name.length();
128: byte[] bname = new byte[len];
129: name.getBytes(0, len, bname, 0);
130: file.write(bname);
131: count++;
132: }
133: if (count != count_in + count_out)
134: throw new Error("internal error writeEndHeaders");
135: long dir_size = file.getFilePointer() - dir_start;
136: for (int i = 4 + ECREC_SIZE; --i >= 0;)
137: buffer[i] = 0;
138: buffer[0] = (byte) 'P';
139: buffer[1] = (byte) 'K';
140: buffer[2] = (byte) '\005';
141: buffer[3] = (byte) '\006';
142: write2(count, buffer, 8); // NUM_ENTRIES_CENTRL_DIR_THS_DISK
143: write2(count, buffer, 10); // TOTAL_ENTRIES_CENTRAL_DIR
144: write4((int) dir_size, buffer, 12); // SIZE_CENTRAL_DIRECTORY
145: write4((int) dir_start, buffer, 16); // OFFSET_START_CENTRAL_DIRECTORY
146: file.write(buffer, 0, 4 + ECREC_SIZE);
147: }
148:
149: ZipMember addMember(String name) {
150: ZipMember zmemb = new ZipMember(name);
151: if (firstEntry == null)
152: firstEntry = zmemb;
153: else
154: lastEntry.next = zmemb;
155: lastEntry = zmemb;
156: return zmemb;
157: }
158:
159: public ZipMember append(String name, byte[] contents)
160: throws IOException {
161: int len = name.length();
162: byte[] bname = new byte[len];
163: name.getBytes(0, len, bname, 0);
164: ZipMember prev = lastEntry;
165: ZipMember zmemb = addMember(name);
166: count_out++;
167: zmemb.compressed_size = contents.length;
168: zmemb.setSize(contents.length);
169: long start = prev == null ? 0 : prev.fileStart()
170: + prev.compressed_size;
171: zmemb.relative_offset_local_header = start;
172: file.seek(start);
173: writeLocalHeader(zmemb);
174: file.write(bname);
175: file.write(contents);
176: return zmemb;
177: }
178:
179: private int readu2() throws IOException {
180: int byte0 = file.read() & 0xFF;
181: int byte1 = file.read() & 0xFF;
182: return (byte1 << 8) | byte0;
183: }
184:
185: private int read4() throws IOException {
186: int byte0 = file.read() & 0xFF;
187: int byte1 = file.read() & 0xFF;
188: int byte2 = file.read() & 0xFF;
189: int byte3 = file.read() & 0xFF;
190: return (byte3 << 24) + (byte2 << 16) + (byte1 << 8) + byte0;
191: }
192:
193: void readDirectory() throws IOException {
194: size = file.length();
195: if (size == 0) {
196: count_in = 0;
197: return;
198: }
199: if (size < ECREC_SIZE + 4)
200: throw new IOException("zipfile too short");
201: file.seek(size - (ECREC_SIZE + 4));
202: if (file.read() != 'P' || file.read() != 'K'
203: || file.read() != '\005' || file.read() != '\006')
204: throw new IOException("not a valid zipfile");
205: file.skipBytes(TOTAL_ENTRIES_CENTRAL_DIR - 4);
206: count_in = readu2(); // Get TOTAL_ENTRIES_CENTRAL_DIR
207: int dir_size = read4(); // Get SIZE_CENTRAL_DIRECTORY
208: file.seek(size - (dir_size + ECREC_SIZE + 4));
209:
210: for (int i = 0; i < count_in; i++) {
211: file.skipBytes(4 + C_COMPRESSED_SIZE);
212: long compressed_size = read4(); // Get C_COMPRESSED_SIZE
213: long uncompressed_size = read4(); // Get C_UNCOMPRESSED_SIZE
214: int filename_length = readu2(); // Get C_FILENAME_LENGTH
215: file.skipBytes(C_RELATIVE_OFFSET_LOCAL_HEADER
216: - (C_FILENAME_LENGTH + 2));
217: long relative_offset_local_header = read4();
218: byte[] name = new byte[filename_length];
219: file.readFully(name);
220: ZipMember zipd = addMember(new String(name, 0));
221: zipd.compressed_size = compressed_size;
222: zipd.setSize(uncompressed_size);
223: zipd.relative_offset_local_header = relative_offset_local_header;
224: }
225: }
226:
227: private static void usage() {
228: System.err.println("zipfile [ptxq] archive [file ...]");
229: System.exit(-1);
230: }
231:
232: public static long copy(InputStream in, OutputStream out,
233: byte[] buffer) throws IOException {
234: long total = 0;
235: for (;;) {
236: int count = in.read(buffer);
237: if (count <= 0)
238: return total;
239: out.write(buffer, 0, count);
240: total += count;
241: }
242: }
243:
244: public static void copy(InputStream in, String name, byte[] buffer)
245: throws IOException {
246: File f = new File(name);
247: String dir_name = f.getParent();
248: if (dir_name != null) {
249: File dir = new File(dir_name);
250: if (!dir.exists())
251: System.err.println("mkdirs:" + dir.mkdirs());
252: }
253: if (name.charAt(name.length() - 1) != '/') {
254: OutputStream out = new BufferedOutputStream(
255: new FileOutputStream(f));
256: copy(in, out, buffer);
257: out.close();
258: }
259: }
260:
261: /**
262: * Manipulate a .zip archive using a tar-like interface.
263: * <p>
264: * Usage: <code>ZipArchive</code> <var>command archive</var> [<var>file</var> ...]
265: * <dl>
266: * <dt><code>ZipArchive t</code> <var>archive file</var> ...<dd>
267: * List information about the named members of the archive.
268: * <dt><code>ZipArchive x</code> <var>archive file</var> ...<dd>
269: * Extract the named members from the archive.
270: * <dt><code>ZipArchive p</code> <var>archive file</var> ...<dd>
271: * Print the named members from the archive on standard output.
272: * Prints just the raw contents, with no headers or conversion.
273: * <dt><code>ZipArchive</code> [<code>ptx</code>] <var>archive</var><dd>
274: * With no arguments, does each command for every member in the archive.
275: * <dt><code>ZipArchive q</code> <var>archive file</var> ...<dd>
276: * Add the named files to the end of archive.
277: * Does not check for duplicates.
278: * </dl>
279: */
280:
281: public static void main(String args[]) throws IOException {
282: if (args.length < 2)
283: usage();
284: String command = args[0];
285: String archive_name = args[1];
286:
287: try {
288: if (command.equals("t") || command.equals("p")
289: || command.equals("x")) {
290: PrintStream out = System.out;
291: byte[] buf = new byte[1024];
292: if (args.length == 2) {
293: BufferedInputStream in = new BufferedInputStream(
294: new FileInputStream(archive_name));
295: ZipInputStream zin = new ZipInputStream(in);
296: ZipEntry zent;
297: while ((zent = zin.getNextEntry()) != null) {
298: String name = zent.getName();
299: if (command.equals("t")) {
300: out.print(name);
301: out.print(" size: ");
302: out.println(zent.getSize());
303: } else if (command.equals("p")) {
304: copy(zin, out, buf);
305: } else // commend.equals("x")
306: {
307: copy(zin, name, buf);
308: }
309: }
310: } else {
311: ZipFile zar = new ZipFile(archive_name);
312: for (int i = 2; i < args.length; i++) {
313: String name = args[i];
314: ZipEntry zent = zar.getEntry(name);
315: if (zent == null) {
316: System.err.println("zipfile "
317: + archive_name + ":" + args[i]
318: + " - not found");
319: System.exit(-1);
320: } else if (command.equals("t")) {
321: out.print(name);
322: out.print(" size: ");
323: out.println(zent.getSize());
324: } else if (command.equals("p")) {
325: copy(zar.getInputStream(zent), out, buf);
326: } else // commend.equals("x")
327: {
328: copy(zar.getInputStream(zent), name, buf);
329: }
330: }
331: }
332: } else if (command.equals("q")) {
333: ZipArchive zar = new ZipArchive(archive_name, "rw");
334: for (int i = 2; i < args.length; i++) {
335: File in = new File(args[i]);
336: if (!in.exists())
337: throw new IOException(args[i] + " - not found");
338: if (!in.canRead())
339: throw new IOException(args[i]
340: + " - not readable");
341: int size = (int) in.length();
342: FileInputStream fin = new FileInputStream(in);
343: byte[] contents = new byte[size];
344: if (fin.read(contents) != size)
345: throw new IOException(args[i] + " - read error");
346: fin.close();
347: zar.append(args[i], contents);
348: }
349: zar.close();
350: } else
351: usage();
352: } catch (IOException ex) {
353: System.err.println("I/O Exception: " + ex);
354: }
355: }
356: }
|