001: /* ===========================================================================
002: * $RCSfile: PatchMaker.java,v $
003: * ===========================================================================
004: *
005: * RetroGuard -- an obfuscation package for Java classfiles.
006: *
007: * Copyright (c) 1998-2006 Mark Welsh (markw@retrologic.com)
008: *
009: * This program can be redistributed and/or modified under the terms of the
010: * Version 2 of the GNU General Public License as published by the Free
011: * Software Foundation.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: */
019:
020: package COM.rl.obf.patch;
021:
022: import java.io.*;
023: import java.util.*;
024: import java.util.zip.*;
025: import java.security.*;
026: import COM.rl.util.*;
027: import COM.rl.util.rfc822.*;
028:
029: /**
030: * Cycle through a Jar file, copying out required entries to a new patch Jar.
031: *
032: * @author Mark Welsh
033: */
034: public class PatchMaker {
035: // Constants -------------------------------------------------------------
036: private static final String STREAM_NAME_MANIFEST = "META-INF/MANIFEST.MF";
037: private static final String MANIFEST_VERSION_TAG = "Manifest-Version";
038: private static final String MANIFEST_VERSION_VALUE = "1.0";
039: private static final String MANIFEST_NAME_TAG = "Name";
040: private static final String MANIFEST_DIGESTALG_TAG = "Digest-Algorithms";
041: private static final String CLASS_EXT = ".class";
042: private static final String SIGNATURE_PREFIX = "META-INF/";
043: private static final String SIGNATURE_EXT = ".SF";
044: private static final String ERROR_CORRUPT_CLASS = "ERROR - corrupt class file: ";
045:
046: // Fields ----------------------------------------------------------------
047: Vector toKeep = null; // Jar entries to be copied
048: private ZipFile inJar; // Old JAR file
049: private SectionList oldManifest; // MANIFEST.MF RFC822 data from old Jar
050: private SectionList newManifest; // MANIFEST.MF RFC822 data for new Jar
051:
052: // Class Methods ---------------------------------------------------------
053:
054: // Instance Methods ------------------------------------------------------
055: /** Ctor. */
056: public PatchMaker(Vector toKeep) {
057: this .toKeep = toKeep;
058: }
059:
060: /** Make a patch Jar from an obfuscated Jar. */
061: public void makePatch(File inFile, File outFile) {
062: try {
063: inJar = new ZipFile(inFile);
064: copyJar(outFile);
065: } catch (Exception e) {
066: inJar = null;
067: }
068: }
069:
070: /** Close input JAR file at GC-time. */
071: protected void finalize() throws Exception {
072: close();
073: }
074:
075: /** Close input JAR file. */
076: public void close() throws Exception {
077: if (inJar != null) {
078: inJar.close();
079: inJar = null;
080: }
081: }
082:
083: // Requested files are copied through unchanged, except for manifest and
084: // any signature files - these are deleted and the manifest is regenerated.
085: private void copyJar(File outFile) throws Exception {
086: parseManifest();
087: Enumeration entries = inJar.entries();
088: ZipOutputStream outJar = null;
089: try {
090: outJar = new ZipOutputStream(new BufferedOutputStream(
091: new FileOutputStream(outFile)));
092: while (entries.hasMoreElements()) {
093: // Get the next entry from the input Jar
094: ZipEntry inEntry = (ZipEntry) entries.nextElement();
095:
096: // Ignore directories
097: if (inEntry.isDirectory()) {
098: continue;
099: }
100:
101: // Open the entry and prepare to process it
102: DataInputStream inStream = null;
103: try {
104: inStream = new DataInputStream(
105: new BufferedInputStream(inJar
106: .getInputStream(inEntry)));
107: String inName = inEntry.getName();
108: if (isToKeep(inName)) {
109: // Copy the entry through unchanged
110: long size = inEntry.getSize();
111: if (size != -1) {
112: byte[] bytes = new byte[(int) size];
113: inStream.readFully(bytes);
114: ZipEntry outEntry = new ZipEntry(inName);
115: outJar.putNextEntry(outEntry);
116:
117: // Pipe OutputStream via digest generators
118: MessageDigest shaDigest = MessageDigest
119: .getInstance("SHA");
120: MessageDigest md5Digest = MessageDigest
121: .getInstance("MD5");
122: DataOutputStream dataOutputStream = new DataOutputStream(
123: new DigestOutputStream(
124: new DigestOutputStream(
125: outJar, shaDigest),
126: md5Digest));
127:
128: // Dump the data, while creating the digests
129: dataOutputStream.write(bytes, 0,
130: bytes.length);
131: dataOutputStream.flush();
132: outJar.closeEntry();
133:
134: // Update the manifest entry with the new digests
135: MessageDigest[] digests = { shaDigest,
136: md5Digest };
137: updateManifest(inName, digests);
138: }
139: }
140: } finally {
141: if (inStream != null) {
142: inStream.close();
143: }
144: }
145: }
146:
147: // Finally, write the new manifest file
148: ZipEntry outEntry = new ZipEntry(STREAM_NAME_MANIFEST);
149: outJar.putNextEntry(outEntry);
150: PrintWriter writer = new PrintWriter(new BufferedWriter(
151: new OutputStreamWriter(outJar)));
152: writer.write(newManifest.toString());
153: writer.flush();
154: outJar.closeEntry();
155: } finally {
156: if (outJar != null) {
157: outJar.close();
158: }
159: }
160: }
161:
162: // Is the entry to be copied?
163: private boolean isToKeep(String name) {
164: // Name is a keeper if: it is directly listed; or, if it is an
165: // inner class (at any depth) of a listed class.
166:
167: // Transform inner class name to outermost class name
168: if (name.length() > CLASS_EXT.length()
169: && name.substring(name.length() - CLASS_EXT.length(),
170: name.length()).equals(CLASS_EXT)
171: && name.indexOf('$') != -1) {
172: name = name.substring(0, name.indexOf('$')) + CLASS_EXT;
173: }
174:
175: // Check for listing
176: for (Enumeration enm = toKeep.elements(); enm.hasMoreElements();) {
177: if (name.equals((String) enm.nextElement())) {
178: return true;
179: }
180: }
181: return false;
182: }
183:
184: // Parse the RFC822-style MANIFEST.MF file
185: private void parseManifest() throws Exception {
186: // The manifest file is called (case insensitively) 'MANIFEST.MF'
187: oldManifest = new SectionList();
188: Enumeration entries = inJar.entries();
189: while (entries.hasMoreElements()) {
190: ZipEntry inEntry = (ZipEntry) entries.nextElement();
191: String name = inEntry.getName();
192: if (STREAM_NAME_MANIFEST.equals(name.toUpperCase())) {
193: oldManifest.parse(inJar.getInputStream(inEntry));
194: break;
195: }
196: }
197:
198: // Create a fresh manifest, with a version header
199: newManifest = new SectionList();
200: Section version = new Section();
201: version.add(MANIFEST_VERSION_TAG, MANIFEST_VERSION_VALUE);
202: newManifest.add(version);
203: }
204:
205: // Update an entry in the manifest file
206: private void updateManifest(String inName, MessageDigest[] digests) {
207: // Create fresh section for entry, and enter "Name" header
208: Section newSection = new Section();
209: newSection.add(MANIFEST_NAME_TAG, inName);
210:
211: // Check for section in old manifest, and copy over non-"Name",
212: // non-digest entries
213: Section oldSection = oldManifest
214: .find(MANIFEST_NAME_TAG, inName);
215: if (oldSection != null) {
216: for (Enumeration enm = oldSection.elements(); enm
217: .hasMoreElements();) {
218: Header header = (Header) enm.nextElement();
219: if (!header.getTag().equals(MANIFEST_NAME_TAG)
220: && header.getTag().indexOf("Digest") == -1) {
221: newSection.add(header);
222: }
223: }
224: }
225:
226: // Create fresh digest entries in the new section
227: if (digests != null && digests.length > 0) {
228: // Digest-Algorithms header
229: StringBuffer sb = new StringBuffer();
230: for (int i = 0; i < digests.length; i++) {
231: sb.append(digests[i].getAlgorithm());
232: sb.append(" ");
233: }
234: newSection.add(MANIFEST_DIGESTALG_TAG, sb.toString());
235:
236: // *-Digest headers
237: for (int i = 0; i < digests.length; i++) {
238: newSection.add(digests[i].getAlgorithm() + "-Digest",
239: Tools.toBase64(digests[i].digest()));
240: }
241: }
242:
243: // Append the new section to the new manifest
244: newManifest.add(newSection);
245: }
246: }
|