001: /* ===========================================================================
002: * $RCSfile: GuardDB.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;
021:
022: import java.io.*;
023: import java.util.*;
024: import java.util.zip.*;
025: import java.security.*;
026: import COM.rl.obf.classfile.*;
027: import COM.rl.util.*;
028: import COM.rl.util.rfc822.*;
029:
030: /**
031: * Classfile database for obfuscation.
032: *
033: * @author Mark Welsh
034: */
035: public class GuardDB implements ClassConstants {
036: // Constants -------------------------------------------------------------
037: private static final String STREAM_NAME_MANIFEST = "META-INF/MANIFEST.MF";
038: private static final String MANIFEST_VERSION_TAG = "Manifest-Version";
039: private static final String MANIFEST_VERSION_VALUE = "1.0";
040: private static final String MANIFEST_NAME_TAG = "Name";
041: private static final String MANIFEST_DIGESTALG_TAG = "Digest-Algorithms";
042: private static final String CLASS_EXT = ".class";
043: private static final String SIGNATURE_PREFIX = "META-INF/";
044: private static final String SIGNATURE_EXT = ".SF";
045: private static final String LOG_MEMORY_USED = "# Memory in use after class data structure built: ";
046: private static final String LOG_MEMORY_TOTAL = "# Total memory available : ";
047: private static final String LOG_MEMORY_BYTES = " bytes";
048: private static final String WARNING_SCRIPT_ENTRY_ABSENT = "# WARNING - identifier from script file not found in JAR: ";
049: private static final String ERROR_CORRUPT_CLASS = "# ERROR - corrupt class file: ";
050: private static final String WARNING_INCOMPATIBLE_VERSION_1 = "# WARNING - class file format has incompatible major-version number: v";
051: private static final String WARNING_INCOMPATIBLE_VERSION_2 = "# WARNING - this version of RetroGuard supports up to class format: v";
052:
053: // Fields ----------------------------------------------------------------
054: private ZipFile inJar; // JAR file for obfuscation
055: private SectionList oldManifest; // MANIFEST.MF RFC822-style data from old Jar
056: private SectionList newManifest; // MANIFEST.MF RFC822-style data for new Jar
057: private ClassTree classTree; // Tree of packages, classes. methods, fields
058: private boolean hasMap = false; // Has the mapping been generated already?
059: private boolean enableMapClassString = false; // Remap strings in reflection?
060: private boolean enableTrim = false; // Trim unused method, field, classes?
061: private boolean enableRepackage = false; // Repackage classes for size?
062: private boolean enableDummySourceFile = false; // Remap SourceFile attribute to text "SourceFile"?
063: private boolean enableDigestSHA = false; // Produce SHA-1 manifest digests?
064: private boolean enableDigestMD5 = false; // Produce MD5 manifest digests?
065:
066: // Class Methods ---------------------------------------------------------
067:
068: // Instance Methods ------------------------------------------------------
069: /** A classfile database for obfuscation. */
070: public GuardDB(File inFile, boolean enableTrim) throws Exception {
071: inJar = new ZipFile(inFile);
072: this .enableTrim = enableTrim;
073: parseManifest();
074: }
075:
076: /** Close input JAR file and log-file at GC-time. */
077: protected void finalize() throws Exception {
078: close();
079: }
080:
081: /** Create a classfile database. */
082: public void buildClassTree(PrintWriter log) throws Exception {
083: // Go through the input Jar, adding each class file to the database
084: int incompatibleVersion = 0;
085: classTree = new ClassTree();
086: Enumeration entries = inJar.entries();
087: while (entries.hasMoreElements()) {
088: // Get the next entry from the input Jar
089: ZipEntry inEntry = (ZipEntry) entries.nextElement();
090: String name = inEntry.getName();
091: if (name.length() > CLASS_EXT.length()
092: && name.substring(
093: name.length() - CLASS_EXT.length(),
094: name.length()).equals(CLASS_EXT)) {
095: // Create a full internal representation of the class file
096: DataInputStream inStream = new DataInputStream(
097: new BufferedInputStream(inJar
098: .getInputStream(inEntry)));
099: ClassFile cf = null;
100: try {
101: cf = ClassFile.create(inStream);
102: } catch (Exception e) {
103: log.println(ERROR_CORRUPT_CLASS + name + " ("
104: + e.getMessage() + ")");
105: } finally {
106: inStream.close();
107: }
108: if (cf.hasIncompatibleVersion()) {
109: incompatibleVersion = cf.getMajorVersion();
110: }
111: classTree.addClassFile(cf, enableTrim);
112: }
113: }
114: // Warn if classes are incompatible version of class file format
115: if (incompatibleVersion != 0) {
116: log.println(WARNING_INCOMPATIBLE_VERSION_1
117: + incompatibleVersion);
118: log.println(WARNING_INCOMPATIBLE_VERSION_2 + MAJOR_VERSION);
119: }
120: }
121:
122: /**
123: * Go through database marking certain entities for retention, while
124: * maintaining polymorphic integrity.
125: */
126: public void retain(RgsEnum rgsEnum, PrintWriter log)
127: throws Exception {
128:
129: // Build database if not already done, or if a mapping has already been generated
130: if (classTree == null || hasMap) {
131: hasMap = false;
132: buildClassTree(log);
133: }
134:
135: // Always retain native methods and their classes, using script entry:
136: // .method;native ** * and_class
137: classTree.retainMethod("**", "*", true, null, false, false,
138: ClassConstants.ACC_NATIVE, ClassConstants.ACC_NATIVE);
139:
140: // Always retain the auto-generated values() and valueOf(...)
141: // methods in Enums, using script entries:
142: // .method;public;static;final **/values * extends java/lang/Enum
143: // .method;public;static **/valueOf * extends java/lang/Enum
144: classTree.retainMethod("**/values", "*", false,
145: "java/lang/Enum", false, false,
146: ClassConstants.ACC_PUBLIC | ClassConstants.ACC_STATIC
147: | ClassConstants.ACC_FINAL,
148: ClassConstants.ACC_PUBLIC | ClassConstants.ACC_STATIC
149: | ClassConstants.ACC_FINAL);
150: classTree.retainMethod("**/valueOf", "*", false,
151: "java/lang/Enum", false, false,
152: ClassConstants.ACC_PUBLIC | ClassConstants.ACC_STATIC,
153: ClassConstants.ACC_PUBLIC | ClassConstants.ACC_STATIC);
154:
155: // Enumerate the entries in the RGS script
156: while (rgsEnum.hasMoreEntries()) {
157: RgsEntry entry = rgsEnum.nextEntry();
158: try {
159: switch (entry.type) {
160: case RgsEntry.TYPE_OPTION:
161: if (ClassConstants.OPTION_DigestSHA
162: .equals(entry.name)) {
163: enableDigestSHA = true;
164: } else if (ClassConstants.OPTION_DigestMD5
165: .equals(entry.name)) {
166: enableDigestMD5 = true;
167: } else if (ClassConstants.OPTION_MapClassString
168: .equals(entry.name)) {
169: enableMapClassString = true;
170: } else if (ClassConstants.OPTION_Trim
171: .equals(entry.name)) {
172: // NOTE - already set in special RGS pass
173: //enableTrim = true;
174: } else if (ClassConstants.OPTION_Repackage
175: .equals(entry.name)) {
176: enableRepackage = true;
177: } else if (ClassConstants.OPTION_Generic
178: .equals(entry.name)) {
179: classTree
180: .retainAttribute(ClassConstants.ATTR_Signature);
181: } else if (ClassConstants.OPTION_LineNumberDebug
182: .equals(entry.name)) {
183: classTree
184: .retainAttribute(ClassConstants.ATTR_LineNumberTable);
185: classTree
186: .retainAttribute(ClassConstants.ATTR_SourceFile);
187: enableDummySourceFile = true;
188: } else if (ClassConstants.OPTION_RuntimeAnnotations
189: .equals(entry.name)) {
190: classTree
191: .retainAttribute(ClassConstants.ATTR_RuntimeVisibleAnnotations);
192: classTree
193: .retainAttribute(ClassConstants.ATTR_RuntimeVisibleParameterAnnotations);
194: classTree
195: .retainAttribute(ClassConstants.ATTR_AnnotationDefault);
196: } else if (ClassConstants.OPTION_Annotations
197: .equals(entry.name)) {
198: classTree
199: .retainAttribute(ClassConstants.ATTR_RuntimeVisibleAnnotations);
200: classTree
201: .retainAttribute(ClassConstants.ATTR_RuntimeInvisibleAnnotations);
202: classTree
203: .retainAttribute(ClassConstants.ATTR_RuntimeVisibleParameterAnnotations);
204: classTree
205: .retainAttribute(ClassConstants.ATTR_RuntimeInvisibleParameterAnnotations);
206: classTree
207: .retainAttribute(ClassConstants.ATTR_AnnotationDefault);
208: } else if (ClassConstants.OPTION_Enumeration
209: .equals(entry.name)) {
210: // .option Enumeration - translates into
211: // .class ** public extends java/lang/Enum
212: classTree.retainClass("**", true, false, false,
213: false, false, "java/lang/Enum", false,
214: false, 0, 0);
215: } else if (ClassConstants.OPTION_Application
216: .equals(entry.name)) {
217: // .option Application - translates into
218: // .method **/main ([Ljava/lang/String;)V and_class
219: classTree.retainMethod("**/main",
220: "([Ljava/lang/String;)V", true, null,
221: false, false, 0, 0);
222: } else if (ClassConstants.OPTION_Applet
223: .equals(entry.name)) {
224: // .option Applet - translates into
225: // .class ** extends java/applet/Applet
226: classTree.retainClass("**", false, false,
227: false, false, false,
228: "java/applet/Applet", false, false, 0,
229: 0);
230: } else if (ClassConstants.OPTION_RMI
231: .equals(entry.name)) {
232: // .option RMI - translates into
233: // .option Serializable (see below for details)
234: // .class ** protected extends java/rmi/Remote
235: // .class **_Stub
236: // .class **_Skel
237: classTree.retainClass("**", false,
238: true, // protected
239: false, false, false, "java/rmi/Remote",
240: false, false, 0, 0);
241: classTree.retainClass("**_Stub", false, false,
242: false, false, false, null, false,
243: false, 0, 0);
244: classTree.retainClass("**_Skel", false, false,
245: false, false, false, null, false,
246: false, 0, 0);
247: }
248: if (ClassConstants.OPTION_Serializable
249: .equals(entry.name)
250: || ClassConstants.OPTION_RMI
251: .equals(entry.name)) {
252: // .option Serializable - translates into
253: // .method;private **/writeObject (Ljava/io/ObjectOutputStream;)V extends java/io/Serializable
254: // .method;private **/readObject (Ljava/io/ObjectInputStream;)V extends java/io/Serializable
255: // .method **/writeReplace ()Ljava/lang/Object; extends java/io/Serializable
256: // .method **/readResolve ()Ljava/lang/Object; extends java/io/Serializable
257: // .field;static;final **/serialVersionUID J extends java/io/Serializable
258: // .field;static;final **/serialPersistentFields [Ljava/io/ObjectStreamField; extends java/io/Serializable
259: // .class ** extends java/io/Serializable
260: // .field;!transient;!static ** * extends java/io/Serializable
261: classTree.retainMethod("**/writeObject",
262: "(Ljava/io/ObjectOutputStream;)V",
263: false, "java/io/Serializable", false,
264: false, ClassConstants.ACC_PRIVATE,
265: ClassConstants.ACC_PRIVATE);
266: classTree.retainMethod("**/readObject",
267: "(Ljava/io/ObjectInputStream;)V",
268: false, "java/io/Serializable", false,
269: false, ClassConstants.ACC_PRIVATE,
270: ClassConstants.ACC_PRIVATE);
271: classTree.retainMethod("**/writeReplace",
272: "()Ljava/lang/Object;", false,
273: "java/io/Serializable", false, false,
274: 0, 0);
275: classTree.retainMethod("**/readResolve",
276: "()Ljava/lang/Object;", false,
277: "java/io/Serializable", false, false,
278: 0, 0);
279: classTree.retainField("**/serialVersionUID",
280: "J", false, "java/io/Serializable",
281: false, false, ClassConstants.ACC_STATIC
282: | ClassConstants.ACC_FINAL,
283: ClassConstants.ACC_STATIC
284: | ClassConstants.ACC_FINAL);
285: classTree.retainField(
286: "**/serialPersistentFields",
287: "[Ljava/io/ObjectStreamField;", false,
288: "java/io/Serializable", false, false,
289: ClassConstants.ACC_PRIVATE
290: | ClassConstants.ACC_STATIC
291: | ClassConstants.ACC_FINAL,
292: ClassConstants.ACC_PRIVATE
293: | ClassConstants.ACC_STATIC
294: | ClassConstants.ACC_FINAL);
295: classTree.retainClass("**", false, false,
296: false, false, false,
297: "java/io/Serializable", false, false,
298: 0, 0);
299: classTree.retainField("**", "*", false,
300: "java/io/Serializable", false, false,
301: ClassConstants.ACC_TRANSIENT
302: | ClassConstants.ACC_STATIC, 0);
303: }
304: break;
305:
306: case RgsEntry.TYPE_ATTR:
307: classTree.retainAttribute(entry.name);
308: break;
309:
310: case RgsEntry.TYPE_NOWARN:
311: classTree.noWarnClass(entry.name);
312: break;
313:
314: case RgsEntry.TYPE_CLASS:
315: case RgsEntry.TYPE_NOTRIM_CLASS:
316: case RgsEntry.TYPE_NOT_CLASS:
317: classTree.retainClass(entry.name,
318: entry.retainToPublic,
319: entry.retainToProtected,
320: entry.retainPubProtOnly,
321: entry.retainFieldsOnly,
322: entry.retainMethodsOnly, entry.extendsName,
323: entry.type == entry.TYPE_NOT_CLASS,
324: entry.type == entry.TYPE_NOTRIM_CLASS,
325: entry.accessMask, entry.accessSetting);
326: break;
327:
328: case RgsEntry.TYPE_METHOD:
329: case RgsEntry.TYPE_NOTRIM_METHOD:
330: case RgsEntry.TYPE_NOT_METHOD:
331: classTree.retainMethod(entry.name,
332: entry.descriptor, entry.retainAndClass,
333: entry.extendsName,
334: entry.type == entry.TYPE_NOT_METHOD,
335: entry.type == entry.TYPE_NOTRIM_METHOD,
336: entry.accessMask, entry.accessSetting);
337: break;
338:
339: case RgsEntry.TYPE_FIELD:
340: case RgsEntry.TYPE_NOT_FIELD:
341: classTree.retainField(entry.name, entry.descriptor,
342: entry.retainAndClass, entry.extendsName,
343: entry.type == entry.TYPE_NOT_FIELD,
344: entry.type == entry.TYPE_NOTRIM_FIELD,
345: entry.accessMask, entry.accessSetting);
346: break;
347:
348: case RgsEntry.TYPE_PACKAGE_MAP:
349: classTree.retainPackageMap(entry.name,
350: entry.obfName);
351: break;
352:
353: case RgsEntry.TYPE_REPACKAGE_MAP:
354: classTree.retainRepackageMap(entry.name,
355: entry.obfName);
356: break;
357:
358: case RgsEntry.TYPE_CLASS_MAP:
359: classTree.retainClassMap(entry.name, entry.obfName);
360: break;
361:
362: case RgsEntry.TYPE_METHOD_MAP:
363: classTree.retainMethodMap(entry.name,
364: entry.descriptor, entry.obfName);
365: break;
366:
367: case RgsEntry.TYPE_FIELD_MAP:
368: classTree.retainFieldMap(entry.name, entry.obfName);
369: break;
370:
371: default:
372: throw new Exception(
373: "Illegal type received from the .rgs script");
374: }
375: } catch (Exception e) {
376: log.println(WARNING_SCRIPT_ENTRY_ABSENT + entry.name);
377: }
378: }
379: }
380:
381: /** Write any non-suppressed warnings to the log. */
382: public void logWarnings(PrintWriter log) throws Exception {
383: if (classTree != null) {
384: classTree.logWarnings(log);
385: }
386: }
387:
388: /** Generate a mapping table for obfuscation. */
389: public void createMap(PrintWriter log) throws Exception {
390: // Build database if not already done
391: if (classTree == null) {
392: buildClassTree(log);
393: }
394:
395: // Traverse the class tree, generating obfuscated names within
396: // package and class namespaces
397: classTree.generateNames(enableRepackage);
398:
399: // Resolve the polymorphic dependencies of each class, generating
400: // non-private method and field names for each namespace
401: classTree.resolveClasses();
402:
403: // Signal that the namespace maps have been created
404: hasMap = true;
405:
406: // Write the memory usage at this point to the log file
407: Runtime rt = Runtime.getRuntime();
408: rt.gc();
409: log.println("#");
410: log.println(LOG_MEMORY_USED
411: + Long.toString(rt.totalMemory() - rt.freeMemory())
412: + LOG_MEMORY_BYTES);
413: log.println(LOG_MEMORY_TOTAL + Long.toString(rt.totalMemory())
414: + LOG_MEMORY_BYTES);
415: log.println("#");
416: }
417:
418: /**
419: * If trim requested, mark all classes, methods, fields for trimming,
420: * then traverse method calls from isFixed methods, untrimming methods,
421: * fields, and classes touched.
422: */
423: public void trim(PrintWriter log) throws Exception {
424: // Generate map table if not already done
425: if (!hasMap) {
426: createMap(log);
427: }
428:
429: if (enableTrim) {
430: // Mark all for trimming
431: classTree.walkTree(new TreeAction() {
432: public void classAction(Cl cl) {
433: cl.setTrimCheck(false);
434: cl.setTrimmed(true);
435: }
436:
437: public void methodAction(Md md) {
438: md.setTrimCheck(false);
439: md.setTrimmed(true);
440: }
441:
442: public void fieldAction(Fd fd) {
443: fd.setTrimCheck(false);
444: fd.setTrimmed(true);
445: }
446:
447: public void packageAction(Pk pk) {
448: }
449: });
450: // Add script-fixed and inheritance-fixed items to stack
451: final TIStack stack = new TIStack();
452: classTree.walkTree(new TreeAction() {
453: public void classAction(Cl cl) {
454: if (cl.isFromScript())
455: stack.push(cl);
456: }
457:
458: public void methodAction(Md md) {
459: if (md.isFromScript())
460: stack.push(md);
461: // NOTE - Push all methods which are overrides indirectly
462: // onto the stack via their parent class (only doing the
463: // actual push later, if their class is referenced). This
464: // is still over-conservative, since the override method
465: // could be safely trimmed if none of its super
466: // implementations is called.
467: if (md.isOverride()) {
468: md.getParent().addRef(md);
469: }
470: }
471:
472: public void fieldAction(Fd fd) {
473: if (fd.isFromScript())
474: stack.push(fd);
475: // NOTE - See above
476: if (fd.isOverride()) {
477: fd.getParent().addRef(fd);
478: }
479: }
480:
481: public void packageAction(Pk pk) {
482: }
483: });
484: // Process each, which will involve the addition and processing
485: // of dependent references
486: while (!stack.empty()) {
487: TreeItem ti = (TreeItem) stack.pop();
488: ti.pushRefs(stack);
489: }
490: }
491: }
492:
493: /** Remap each class based on the remap database, and remove attributes. */
494: public void remapTo(File out, PrintWriter log) throws Exception {
495: // Generate map table if not already done
496: if (!hasMap) {
497: createMap(log);
498: }
499:
500: // Write the name frequency and name mapping table to the log file
501: classTree.dump(log);
502:
503: // Go through the input Jar, removing attributes and remapping the Constant Pool
504: // for each class file. Other files are copied through unchanged, except for manifest
505: // and any signature files - these are deleted and the manifest is regenerated.
506: Enumeration entries = inJar.entries();
507: ZipOutputStream outJar = null;
508: try {
509: outJar = new ZipOutputStream(new BufferedOutputStream(
510: new FileOutputStream(out)));
511: // No comment in Pro, to reduce output jar size
512: if (Version.isLite) {
513: outJar.setComment(Version.getJarComment());
514: }
515: while (entries.hasMoreElements()) {
516: // Get the next entry from the input Jar
517: ZipEntry inEntry = (ZipEntry) entries.nextElement();
518:
519: // Ignore directories
520: if (inEntry.isDirectory()) {
521: continue;
522: }
523:
524: // Open the entry and prepare to process it
525: DataInputStream inStream = null;
526: try {
527: inStream = new DataInputStream(
528: new BufferedInputStream(inJar
529: .getInputStream(inEntry)));
530: String inName = inEntry.getName();
531: if (inName.length() > CLASS_EXT.length()
532: && inName.substring(
533: inName.length()
534: - CLASS_EXT.length(),
535: inName.length()).equals(CLASS_EXT)) {
536: // Write obfuscated class to the output Jar
537: ClassFile cf = ClassFile.create(inStream);
538: // To reduce output jar size in Pro, no class ID string
539: if (Version.isLite) {
540: cf.setIdString(Version.getClassIdString());
541: }
542: Cl cl = classTree.getCl(cf.getName());
543: // Trim entire class if requested
544: if (cl != null && !cl.isTrimmed()) {
545: cf.trimAttrs(classTree);
546: cl.trimClassFile(cf);
547: cf.updateRefCount();
548: cf.remap(classTree, log,
549: enableMapClassString,
550: enableDummySourceFile);
551: ZipEntry outEntry = new ZipEntry(cf
552: .getName()
553: + CLASS_EXT);
554: outJar.putNextEntry(outEntry);
555:
556: // Create an OutputStream piped through a number of digest generators for the manifest
557: MessageDigest shaDigest = null;
558: MessageDigest md5Digest = null;
559: OutputStream outputStream = outJar;
560: if (enableDigestSHA) {
561: shaDigest = MessageDigest
562: .getInstance("SHA-1");
563: outputStream = new DigestOutputStream(
564: outputStream, shaDigest);
565: }
566: if (enableDigestMD5) {
567: md5Digest = MessageDigest
568: .getInstance("MD5");
569: outputStream = new DigestOutputStream(
570: outputStream, md5Digest);
571: }
572: DataOutputStream dataOutputStream = new DataOutputStream(
573: outputStream);
574:
575: // Dump the classfile, while creating the digests
576: cf.write(dataOutputStream);
577: dataOutputStream.flush();
578: outJar.closeEntry();
579:
580: // Now update the manifest entry for the class with new name and new digests
581: MessageDigest[] digests = { shaDigest,
582: md5Digest };
583: updateManifest(inName, cf.getName()
584: + CLASS_EXT, digests);
585: }
586: } else if (STREAM_NAME_MANIFEST.equals(inName
587: .toUpperCase())
588: || (inName.length() > (SIGNATURE_PREFIX
589: .length() + 1 + SIGNATURE_EXT
590: .length())
591: && inName.indexOf(SIGNATURE_PREFIX) != -1 && inName
592: .substring(
593: inName.length()
594: - SIGNATURE_EXT
595: .length(),
596: inName.length()).equals(
597: SIGNATURE_EXT))) {
598: // Don't pass through the manifest or signature files
599: continue;
600: } else {
601: // Copy the non-class entry through unchanged
602: long size = inEntry.getSize();
603: if (size != -1) {
604: byte[] bytes = new byte[(int) size];
605: inStream.readFully(bytes);
606: String outName = classTree
607: .getOutName(inName);
608: ZipEntry outEntry = new ZipEntry(outName);
609: outJar.putNextEntry(outEntry);
610:
611: // Create an OutputStream piped through a number of digest generators for the manifest
612: MessageDigest shaDigest = null;
613: MessageDigest md5Digest = null;
614: OutputStream outputStream = outJar;
615: if (enableDigestSHA) {
616: shaDigest = MessageDigest
617: .getInstance("SHA-1");
618: outputStream = new DigestOutputStream(
619: outputStream, shaDigest);
620: }
621: if (enableDigestMD5) {
622: md5Digest = MessageDigest
623: .getInstance("MD5");
624: outputStream = new DigestOutputStream(
625: outputStream, md5Digest);
626: }
627: DataOutputStream dataOutputStream = new DataOutputStream(
628: outputStream);
629:
630: // Dump the data, while creating the digests
631: dataOutputStream.write(bytes, 0,
632: bytes.length);
633: dataOutputStream.flush();
634: outJar.closeEntry();
635:
636: // Now update the manifest entry for the entry with new name and new digests
637: MessageDigest[] digests = { shaDigest,
638: md5Digest };
639: updateManifest(inName, outName, digests);
640: }
641: }
642: } finally {
643: if (inStream != null) {
644: inStream.close();
645: }
646: }
647: }
648:
649: // Finally, write the new manifest file
650: ZipEntry outEntry = new ZipEntry(STREAM_NAME_MANIFEST);
651: outJar.putNextEntry(outEntry);
652: PrintWriter writer = new PrintWriter(new BufferedWriter(
653: new OutputStreamWriter(outJar)));
654: newManifest.writeString(writer);
655: writer.flush();
656: outJar.closeEntry();
657: } finally {
658: if (outJar != null) {
659: outJar.close();
660: }
661: }
662: }
663:
664: /** Close input JAR file. */
665: public void close() throws Exception {
666: if (inJar != null) {
667: inJar.close();
668: inJar = null;
669: }
670: }
671:
672: // Parse the RFC822-style MANIFEST.MF file
673: private void parseManifest() throws Exception {
674: // The manifest file is the first in the jar and is called
675: // (case insensitively) 'MANIFEST.MF'
676: oldManifest = new SectionList();
677: Enumeration entries = inJar.entries();
678: while (entries.hasMoreElements()) {
679: // Get the first entry only from the input Jar
680: ZipEntry inEntry = (ZipEntry) entries.nextElement();
681: String name = inEntry.getName();
682: if (STREAM_NAME_MANIFEST.equals(name.toUpperCase())) {
683: oldManifest.parse(inJar.getInputStream(inEntry));
684: break;
685: }
686: }
687:
688: // Create a fresh manifest, with a version header
689: newManifest = new SectionList();
690: Section version = oldManifest.find(MANIFEST_VERSION_TAG,
691: MANIFEST_VERSION_VALUE);
692: if (version == null) {
693: version = new Section();
694: version.add(MANIFEST_VERSION_TAG, MANIFEST_VERSION_VALUE);
695: }
696: newManifest.add(version);
697:
698: // copy through all the none-filename sections, apart from the version
699: for (Enumeration enm = oldManifest.elements(); enm
700: .hasMoreElements();) {
701: Section section = (Section) enm.nextElement();
702: if (section != null && section != version) {
703: Header name = section.findTag(MANIFEST_NAME_TAG);
704: if (name == null) {
705: newManifest.add(section);
706: } else {
707: String value = name.getValue();
708: if (value.length() > 0
709: && value.charAt(value.length() - 1) == '/') {
710: newManifest.add(section);
711: }
712: }
713: }
714: }
715: }
716:
717: // Update an entry in the manifest file
718: private void updateManifest(String inName, String outName,
719: MessageDigest[] digests) {
720: // Check for section in old manifest
721: Section oldSection = oldManifest
722: .find(MANIFEST_NAME_TAG, inName);
723: if (oldSection != null) {
724: // Create fresh section for entry, and enter "Name" header
725: Section newSection = new Section();
726: newSection.add(MANIFEST_NAME_TAG, outName);
727:
728: // Copy over non-"Name", non-digest entries
729: for (Enumeration enm = oldSection.elements(); enm
730: .hasMoreElements();) {
731: Header header = (Header) enm.nextElement();
732: if (!header.getTag().equals(MANIFEST_NAME_TAG)
733: && header.getTag().indexOf("Digest") == -1) {
734: newSection.add(header);
735: }
736: }
737:
738: // Create fresh digest entries in the new section
739: if (digests != null && digests.length > 0) {
740: // Digest-Algorithms header
741: StringBuffer sb = new StringBuffer();
742: for (int i = 0; i < digests.length; i++) {
743: if (digests[i] != null) {
744: sb.append(digests[i].getAlgorithm());
745: sb.append(" ");
746: }
747: }
748: if (sb.length() > 0) {
749: newSection.add(MANIFEST_DIGESTALG_TAG, sb
750: .toString());
751: }
752:
753: // *-Digest headers
754: for (int i = 0; i < digests.length; i++) {
755: if (digests[i] != null) {
756: newSection.add(digests[i].getAlgorithm()
757: + "-Digest", Tools.toBase64(digests[i]
758: .digest()));
759: }
760: }
761: }
762:
763: // Append the new section to the new manifest
764: newManifest.add(newSection);
765: }
766: }
767: }
768:
769: // Stack used for marking TreeItems that must not be trimmed
770: class TIStack extends Stack {
771: public Object push(Object o) {
772: try {
773: if (o != null) {
774: TreeItem ti = (TreeItem) o;
775: if (ti instanceof Cl) {
776: pushClTree((Cl) ti);
777: } else if (ti instanceof MdFd) {
778: MdFd mdfd = (MdFd) ti;
779: // Preserve class if method or field is static
780: Cl cl = (Cl) mdfd.getParent();
781: if (mdfd.isStatic()) {
782: pushClTree(cl);
783: }
784: if (mdfd instanceof Fd) {
785: pushFdGroup(cl, mdfd.getInName());
786: } else // method
787: {
788: Md md = (Md) mdfd;
789: String mdName = md.getInName();
790: // Treat special methods (<init> <clinit>) differently
791: if (mdName.charAt(0) == '<') {
792: pushItem(md);
793: } else {
794: pushMdGroup(cl, mdName, md.getDescriptor());
795: }
796: }
797: }
798: }
799: } catch (Exception e) { /* ignore */
800: }
801: return o;
802: }
803:
804: // Push class and all supers onto trim-preserve stack
805: private Object pushClTree(Cl cl) throws Exception {
806: if (cl != null) {
807: pushItem(cl);
808: // Propagate up supers in jar
809: pushClTree(cl.getSuperCl());
810: for (Enumeration enm = cl.getSuperInterfaces(); enm
811: .hasMoreElements(); pushClTree((Cl) enm
812: .nextElement()))
813: ;
814: }
815: return cl;
816: }
817:
818: // Push item onto trim-preserve stack
819: private void pushItem(TreeItem ti) {
820: if (ti != null && !ti.isTrimCheck()) {
821: ti.setTrimCheck(true);
822: ti.setTrimmed(false);
823: super .push(ti);
824: }
825: }
826:
827: // Push method across inheritance group onto trim-preserve stack
828: private void pushMdGroup(Cl cl, String mdName, String mdDesc)
829: throws Exception {
830: pushMdFdGroup(cl, mdName, mdDesc);
831: }
832:
833: // Push field across inheritance group onto trim-preserve stack
834: private void pushFdGroup(Cl cl, String fdName) throws Exception {
835: pushMdFdGroup(cl, fdName, null);
836: }
837:
838: private void pushMdFdGroup(Cl cl, String name, String desc)
839: throws Exception {
840: if (cl != null) {
841: final String fname = name;
842: final String fdesc = desc;
843: cl.walkGroup(new TreeAction() {
844: public void classAction(Cl cl) {
845: try {
846: MdFd mdfd = (fdesc == null ? (MdFd) cl
847: .getField(fname) : (MdFd) cl.getMethod(
848: fname, fdesc));
849: pushItem(mdfd);
850: } catch (Exception e) { /* ignore */
851: }
852: }
853: });
854: }
855: }
856: }
|