1 package com.randomnoun.common;
2
3
4
5
6
7 import java.io.BufferedReader;
8 import java.io.EOFException;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.FileNotFoundException;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.io.LineNumberReader;
17 import java.security.MessageDigest;
18 import java.security.NoSuchAlgorithmException;
19 import java.text.SimpleDateFormat;
20 import java.util.ArrayList;
21 import java.util.Date;
22 import java.util.List;
23 import java.util.regex.Pattern;
24 import java.util.zip.ZipEntry;
25 import java.util.zip.ZipException;
26 import java.util.zip.ZipInputStream;
27
28 import org.apache.log4j.Logger;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public class ResourceFinder {
78
79
80 Logger logger = Logger.getLogger(ResourceFinder.class);
81
82
83
84
85
86 public final static int MATCHTYPE_CONTAINS = 0;
87
88
89
90
91
92 public final static int MATCHTYPE_STARTSWITH = 1;
93
94
95
96
97
98 public final static int MATCHTYPE_EXACT = 2;
99
100
101
102
103
104 public final static int MATCHTYPE_REGEX = 3;
105
106
107 private String searchTerm;
108
109
110 private Pattern searchPattern;
111
112
113 private File startDirectory;
114
115
116 private ZipInputStream startInputStream;
117
118
119 private int matchType;
120
121
122 private boolean ignoreCase = false;
123
124
125 private boolean followSymlinks = false;
126
127
128
129 private boolean showAll = false;
130
131
132 private boolean ignoreErrors = false;
133
134
135 private long maxArchiveDepth = -1;
136
137
138 private long maxFolderDepth = -1;
139
140
141 private long currentArchiveDepth = -1;
142
143
144 private long currentFolderDepth = -1;
145
146
147 private ResourceFinderCallback callback;
148
149
150 private transient boolean abort = false;
151
152
153
154
155 private Pattern isArchivePattern = Pattern.compile(
156 ".*\\.([Zz][Ii][Pp]|" +
157 "[SsJjWwEeRrHh][Aa][Rr])$");
158
159
160 public void abort() { this.abort = true; }
161
162
163 public File getStartDirectory() { return startDirectory; }
164
165
166 public InputStream getStartInputStream() { return startInputStream; }
167
168
169
170
171
172
173
174 public boolean matches(String resourceName) {
175 if (resourceName==null) { throw new NullPointerException("null string"); }
176 if (ignoreCase) {
177 switch(matchType) {
178 case MATCHTYPE_EXACT: return searchTerm.equalsIgnoreCase(resourceName);
179 case MATCHTYPE_REGEX: return searchPattern.matcher(resourceName).find();
180 case MATCHTYPE_STARTSWITH: return resourceName.toUpperCase().startsWith(searchTerm);
181 case MATCHTYPE_CONTAINS: return resourceName.toUpperCase().contains(searchTerm);
182 default: throw new IllegalStateException("Illegal matchType '" + matchType + "'");
183 }
184 } else {
185 switch(matchType) {
186 case MATCHTYPE_EXACT: return searchTerm.equals(resourceName);
187 case MATCHTYPE_REGEX: return searchPattern.matcher(resourceName).find();
188 case MATCHTYPE_STARTSWITH: return resourceName.startsWith(searchTerm);
189 case MATCHTYPE_CONTAINS: return resourceName.contains(searchTerm);
190 default: throw new IllegalStateException("Illegal matchType '" + matchType + "'");
191 }
192 }
193 }
194
195
196
197
198
199 public static class HashGeneratingInputStream extends InputStream {
200 InputStream is;
201 MessageDigest algorithm1, algorithm2;
202
203 public HashGeneratingInputStream(InputStream is) {
204 this.is = is;
205 try {
206 algorithm1 = MessageDigest.getInstance("MD5");
207 algorithm2 = MessageDigest.getInstance("SHA-1");
208 } catch (NoSuchAlgorithmException nsae) {
209 throw (IllegalStateException) new IllegalStateException(
210 "Invalid crypto config").initCause(nsae);
211 }
212 algorithm1.reset();
213 algorithm2.reset();
214
215 }
216
217 @Override
218 public int read() throws IOException {
219 int result = is.read();
220 if (result != -1) {
221 algorithm1.update((byte) result);
222 algorithm2.update((byte) result);
223 }
224 return result;
225 }
226 public int available() throws IOException {
227 return is.available();
228 }
229 public void close() {
230
231 }
232 public void mark(int readlimit) {
233 is.mark(readlimit);
234 }
235 public boolean markSupported() {
236 return is.markSupported();
237 }
238 public int read(byte[] b) throws IOException {
239 int result = is.read(b);
240 if (result!=-1) {
241 algorithm1.update(b, 0, result);
242 algorithm2.update(b, 0, result);
243 }
244 return result;
245 }
246 public int read(byte[] b, int off, int len) throws IOException {
247 int result = is.read(b, off, len);
248 if (result!=-1) {
249 algorithm1.update(b, off, result);
250 algorithm2.update(b, off, result);
251 }
252 return result;
253 }
254 public void reset() throws IOException {
255 is.reset();
256 }
257 public long skip(long n) throws IOException {
258 return is.skip(n);
259 }
260
261
262 public String getMd5() {
263 byte messageDigest[] = algorithm1.digest();
264
265 StringBuffer hexString = new StringBuffer();
266 for (int i=0;i<messageDigest.length;i++) {
267 hexString.append(Integer.toString( ( messageDigest[i] & 0xff ) + 0x100, 16).substring( 1 ));
268 }
269 return hexString.toString();
270 }
271
272
273 public String getSha1() {
274 byte messageDigest[] = algorithm2.digest();
275
276 StringBuffer hexString = new StringBuffer();
277 for (int i=0;i<messageDigest.length;i++) {
278 hexString.append(Integer.toString( ( messageDigest[i] & 0xff ) + 0x100, 16).substring( 1 ));
279 }
280 return hexString.toString();
281 }
282 }
283
284
285 public static class ResourceFinderCallbackResult {
286 boolean abort = false;
287 InputStream replaceInputStream = null;
288 }
289
290
291
292
293 public interface ResourceFinderCallback {
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311 public ResourceFinderCallbackResult preProcess(String resourceName, long filesize, long timestamp,
312 InputStream inputStream) throws IOException;
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 public ResourceFinderCallbackResult postProcess(String resourceName, long filesize, long timestamp,
330 InputStream inputStream, ResourceFinderCallbackResult preProcessResult) throws IOException;
331
332 }
333
334
335
336 public static class HashingResourceFinderCallback implements ResourceFinderCallback {
337
338 int resourceIndex = 0;
339 boolean showHashes = false;
340 boolean ignoreErrors = false;
341
342 public HashingResourceFinderCallback(boolean ignoreErrors) {
343 this.ignoreErrors = ignoreErrors;
344 }
345
346 public void ignorableException(String message, Exception e) throws ZipException {
347 if (ignoreErrors) {
348 Logger.getLogger(HashingResourceFinderCallback.class).error(message, e);
349 } else {
350 throw (ZipException) new ZipException(message).initCause(e);
351 }
352 }
353
354 public ResourceFinderCallbackResult preProcess(String resourceName, long filesize, long timestamp, InputStream inputStream) throws IOException {
355 ResourceFinderCallbackResult rfcbr = new ResourceFinderCallbackResult();
356 rfcbr.replaceInputStream = new HashGeneratingInputStream(inputStream);
357 return rfcbr;
358 }
359
360 public ResourceFinderCallbackResult postProcess(String resourceName, long filesize, long timestamp, InputStream inputStream, ResourceFinderCallbackResult preProcessResult) throws IOException {
361 ResourceFinderCallbackResult rfcbr = new ResourceFinderCallbackResult();
362 try {
363 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
364 String md5, sha1;
365 if (inputStream instanceof HashGeneratingInputStream) {
366 HashGeneratingInputStream hgis = (HashGeneratingInputStream) inputStream;
367
368 byte[] buffer = new byte[4096];
369 try {
370
371 while (inputStream.read(buffer) != -1) { }
372 } catch (EOFException eofe) {
373 ignorableException("Error reading zip resource '" + resourceName + "' for hash", eofe);
374 } catch (ZipException ze) {
375
376 ignorableException("Error reading zip resource '" + resourceName + "' for hash", ze);
377 }
378
379 md5 = hgis.getMd5();
380 sha1 = hgis.getSha1();
381 } else {
382 MessageDigest algorithm1, algorithm2;
383 try {
384 algorithm1 = MessageDigest.getInstance("MD5");
385 algorithm2 = MessageDigest.getInstance("SHA1");
386 } catch (NoSuchAlgorithmException nsae) {
387 throw (IllegalStateException) new IllegalStateException(
388 "Invalid crypto config").initCause(nsae);
389 }
390 algorithm1.reset();
391 algorithm2.reset();
392 byte[] buffer = new byte[4096];
393 int bytesRead;
394 try {
395 while ((bytesRead = inputStream.read(buffer)) != -1) {
396 algorithm1.update(buffer, 0, bytesRead);
397 algorithm2.update(buffer, 0, bytesRead);
398 }
399 } catch (EOFException eofe) {
400
401 ignorableException("Error hashing zip resource '" + resourceName + "'", eofe);
402 } catch (ZipException ze) {
403 ignorableException("Error hashing zip resource '" + resourceName + "'", ze);
404 }
405 byte messageDigest1[] = algorithm1.digest();
406 byte messageDigest2[] = algorithm2.digest();
407 StringBuffer hexString1 = new StringBuffer();
408 StringBuffer hexString2 = new StringBuffer();
409 for (int i=0; i<messageDigest1.length; i++) {
410 hexString1.append(Integer.toString( ( messageDigest1[i] & 0xff ) + 0x100, 16).substring( 1 ));
411 }
412 for (int i=0; i<messageDigest2.length; i++) {
413 hexString2.append(Integer.toString( ( messageDigest2[i] & 0xff ) + 0x100, 16).substring( 1 ));
414 }
415 md5 = hexString1.toString();
416 sha1 = hexString2.toString();
417 }
418 System.out.println("[" + resourceIndex + "] " + resourceName + " " + (filesize==-1 ? "(unknown)" : String.valueOf(filesize)) +
419 " " + sdf.format(new Date(timestamp)) + " " + md5 + " " + sha1 );
420 } catch (IllegalArgumentException iae) {
421
422 throw new IOException("IllegalArgumentException processing ZipInputStream", iae);
423 }
424 resourceIndex++;
425 return rfcbr;
426 }
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 public static class DisplayResourceFinderCallback implements ResourceFinderCallback {
443
444 public final static int DUMP_NAMES = -1;
445 public final static int DUMP_NAMES_AND_RESOURCES = -2;
446 public final static int DUMP_RESOURCES = 0;
447
448 int dumpType = 0;
449 List<Integer> dumpResourceNumbers;
450 int maxDumpResourceNumber = -1;
451 int resourceIndex = 0;
452 boolean verbose = false;
453 boolean manifests = false;
454 boolean decompile = false;
455 String searchContents = null;
456 boolean searchContentsIgnoreCase = false;
457
458
459 public DisplayResourceFinderCallback(int dumpType, List<Integer> dumpResourceNumbers, boolean verbose, boolean manifests, boolean decompile, String searchContents, boolean searchContentsIgnoreCase) {
460
461 this.dumpResourceNumbers = dumpResourceNumbers;
462 this.dumpType = dumpType;
463 this.verbose = verbose;
464 this.manifests = manifests;
465 this.decompile = decompile;
466 this.searchContents = searchContents;
467 this.searchContentsIgnoreCase = searchContentsIgnoreCase;
468 if (dumpResourceNumbers != null) {
469 for (Integer drn : dumpResourceNumbers) {
470 maxDumpResourceNumber = Math.max(maxDumpResourceNumber, drn.intValue());
471 }
472 }
473 }
474
475 private ResourceFinderCallbackResult dump(String resourceName, long filesize, long timestamp, InputStream inputStream) throws IOException {
476 ResourceFinderCallbackResult rfcr = new ResourceFinderCallbackResult();
477 if ((dumpType == DUMP_NAMES_AND_RESOURCES || dumpType == DUMP_NAMES) &&
478 (dumpResourceNumbers==null || dumpResourceNumbers.contains(new Integer(resourceIndex)))
479 )
480 {
481 if (verbose) {
482 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
483 System.out.println("[" + resourceIndex + "] " + resourceName + " " + (filesize==-1 ? "(unknown)" : String.valueOf(filesize)) + " " + sdf.format(new Date(timestamp)));
484 } else if (searchContents == null) {
485 System.out.println("[" + resourceIndex + "] " + resourceName);
486 } else if (searchContents != null) {
487 int pos = -1;
488
489 LineNumberReader lnr = new LineNumberReader(new InputStreamReader(inputStream));
490 if (searchContentsIgnoreCase) {
491 searchContents = searchContents.toLowerCase();
492 String line = lnr.readLine();
493 if (line!=null) {
494 pos = line.toString().toLowerCase().indexOf(searchContents.toLowerCase());
495 while (line!=null && pos==-1) {
496 line = lnr.readLine();
497 pos = line==null ? -1 : line.toString().toLowerCase().indexOf(searchContents.toLowerCase());
498 }
499 }
500 } else {
501 String line = lnr.readLine();
502 if (line!=null) {
503 pos = line.toString().indexOf(searchContents);
504 while (line!=null && pos==-1) {
505 line = lnr.readLine();
506 pos = line==null ? -1 : line.toString().indexOf(searchContents);
507 }
508 }
509 }
510 if (pos!=-1) {
511 System.out.println("[" + resourceIndex + "] [line " + lnr.getLineNumber() + ", col " + pos + "] " + resourceName);
512 }
513 }
514 }
515 if ((dumpType == DUMP_NAMES_AND_RESOURCES || dumpType == DUMP_RESOURCES) &&
516 (dumpResourceNumbers==null || dumpResourceNumbers.contains(new Integer(resourceIndex)))
517 ) {
518
519 if (manifests) {
520 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
521 String line = br.readLine();
522 while (line != null) {
523 int len = line.length();
524 while (line.length() > 0 && line.charAt(0) == ' ') {
525 line = line.substring(1);
526 }
527 System.out.print(line);
528 if (len != 70) { System.out.println(); }
529 line = br.readLine();
530 }
531 } else if (decompile) {
532
533
534 File tmpFile = File.createTempFile("resourceFinder", ".class");
535 FileOutputStream fos = new FileOutputStream(tmpFile);
536 StreamUtil.copyStream(inputStream, fos, 1024);
537 fos.close();
538 try {
539 ProcessUtil processUtil = new ProcessUtil();
540 String result = processUtil.exec(new String[] { "jad", "-lnc", "-p", tmpFile.getCanonicalPath() });
541 System.out.println(result);
542 } catch (ProcessUtil.ProcessException pe) {
543 throw (IOException) new IOException("Problem executing jad").initCause(pe);
544 }
545
546 } else {
547 StreamUtil.copyStream(inputStream, System.out, 1024);
548 }
549 if (resourceIndex == maxDumpResourceNumber) {
550
551 rfcr.abort = true;
552 }
553
554
555
556 if (dumpType == DUMP_NAMES_AND_RESOURCES &&
557 (dumpResourceNumbers==null || dumpResourceNumbers.size()>1)) {
558 System.out.println();
559 }
560 }
561 resourceIndex++;
562 return rfcr;
563 }
564
565 public ResourceFinderCallbackResult preProcess(String resourceName, long filesize, long timestamp, InputStream inputStream) throws IOException {
566 return dump(resourceName, filesize, timestamp, inputStream);
567 }
568
569
570 public ResourceFinderCallbackResult postProcess(String resourceName, long filesize, long timestamp, InputStream inputStream, ResourceFinderCallbackResult preProcessResult) throws IOException {
571 if (preProcessResult==null) { return dump(resourceName, filesize, timestamp, inputStream); }
572 return null;
573 }
574
575
576 }
577
578
579
580
581
582
583
584
585
586
587
588 public ResourceFinder(String searchTerm, int matchType, boolean ignoreCase, File startDirectory, ResourceFinderCallback callback) throws IOException {
589 init(searchTerm, matchType, ignoreCase, callback);
590 this.startDirectory = startDirectory.getCanonicalFile();
591
592 }
593
594
595 private void init(String searchTerm, int matchType, boolean ignoreCase,
596 ResourceFinderCallback callback)
597 {
598 this.matchType = matchType;
599 this.ignoreCase = ignoreCase;
600 this.showAll = false;
601 this.ignoreErrors = false;
602 this.callback = callback;
603
604
605 if (ignoreCase) {
606 switch (matchType) {
607 case MATCHTYPE_EXACT: break;
608 case MATCHTYPE_REGEX: searchPattern = Pattern.compile(searchTerm, Pattern.CASE_INSENSITIVE); break;
609 case MATCHTYPE_STARTSWITH: searchTerm = searchTerm.toUpperCase(); break;
610 case MATCHTYPE_CONTAINS: searchTerm = searchTerm.toUpperCase(); break;
611 default: throw new IllegalStateException("Illegal matchType '" + matchType + "'");
612 }
613 } else {
614 switch (matchType) {
615 case MATCHTYPE_EXACT: break;
616 case MATCHTYPE_REGEX: searchPattern = Pattern.compile(searchTerm); break;
617 case MATCHTYPE_STARTSWITH: break;
618 case MATCHTYPE_CONTAINS: break;
619 default: throw new IllegalStateException("Illegal matchType '" + matchType + "'");
620 }
621 }
622 this.searchTerm = searchTerm;
623
624 }
625
626
627
628
629
630
631
632 public void setFollowSymLinks(boolean followSymlinks) {
633 this.followSymlinks = followSymlinks;
634 }
635
636
637
638
639
640
641
642 public boolean getFollowSymlinks() {
643 return followSymlinks;
644 }
645
646
647
648
649
650
651
652
653 public void setShowAll(boolean showAll) {
654 this.showAll = showAll;
655 }
656
657
658
659
660
661
662
663 public boolean getShowAll() {
664 return showAll;
665 }
666
667
668
669
670
671
672
673
674
675
676
677
678 public void setIgnoreErrors(boolean ignoreErrors) {
679 this.ignoreErrors = ignoreErrors;
680 }
681
682
683
684
685
686
687
688 public boolean getIgnoreErrors() {
689 return ignoreErrors;
690 }
691
692
693
694
695
696
697
698
699
700
701
702
703 public ResourceFinder(String searchTerm, int matchType, boolean ignoreCase, ZipInputStream startInputStream, ResourceFinderCallback callback) throws IOException {
704 init(searchTerm, matchType, ignoreCase, callback);
705 this.startInputStream = startInputStream;
706 }
707
708
709
710
711
712
713
714
715 public void find() throws IOException {
716 this.currentArchiveDepth = -1;
717 if (startInputStream != null) {
718
719 findResourceInZip(startInputStream, "#");
720 return;
721
722 } else if (startDirectory.isFile()) {
723
724 File file = startDirectory;
725 String name = file.getName();
726 if (!followSymlinks && isLink(file )) {
727
728
729 } else if (matches(name)) {
730 FileInputStream fis = new FileInputStream(file);
731 callback.postProcess(name, file.length(), file.lastModified(), fis, null);
732 fis.close();
733
734
735
736
737
738
739
740 } else if (isArchive(name)) {
741 if (showAll) {
742 FileInputStream fis = new FileInputStream(file);
743 callback.postProcess(name, file.length(), file.lastModified(), fis, null);
744 fis.close();
745 }
746 ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
747 zis.close();
748 } else {
749 if (showAll) {
750 FileInputStream fis = new FileInputStream(file);
751 callback.postProcess(name, file.length(), file.lastModified(), fis, null);
752 fis.close();
753 }
754 }
755 return;
756 } else {
757 findResourceInFolder(startDirectory, "");
758 }
759 }
760
761
762
763
764
765
766
767 public boolean isArchive(String name) {
768 return isArchivePattern.matcher(name).matches();
769 }
770
771
772
773
774
775
776
777
778 public static boolean isLink(File file) throws IOException {
779 try {
780 if (!file.exists()) {
781 return true;
782 } else {
783 String cnnpath = file.getCanonicalPath();
784 String abspath = file.getAbsolutePath();
785 return !abspath.equals(cnnpath);
786 }
787 } catch(IOException ex) {
788
789 return true;
790 }
791 }
792
793
794
795
796
797
798
799
800
801
802 public void findResourceInFolder(File folder, String prefix) throws IOException {
803 if (maxArchiveDepth!=-1 && currentArchiveDepth>=maxArchiveDepth) {
804 return;
805 }
806
807 currentArchiveDepth++;
808
809 File[] folderContents = folder.listFiles();
810 if (folderContents != null) {
811 for (File file : folderContents) {
812
813
814
815
816 String name = file.getName();
817
818 if (!followSymlinks && isLink(file)) {
819
820
821 } else if (file.isDirectory() && (maxFolderDepth==-1 || currentFolderDepth+1 <= maxFolderDepth)) {
822 currentFolderDepth++;
823 findResourceInFolder(file, prefix + name + "/");
824 currentFolderDepth--;
825
826 } else if (matches(name)) {
827 FileInputStream fis = new FileInputStream(file);
828 callback.postProcess(prefix + name, file.length(), file.lastModified(), fis, null);
829 fis.close();
830
831 if (isArchive(name)) {
832 fis = new FileInputStream(file);
833 ZipInputStream zis = new ZipInputStream(fis);
834 findResourceInZip(zis, prefix + name + "#");
835 fis.close();
836 }
837 } else if (isArchive(name)) {
838 if (showAll) {
839 FileInputStream fis = new FileInputStream(file);
840 callback.postProcess(prefix + name, file.length(), file.lastModified(), fis, null);
841 fis.close();
842 }
843 FileInputStream fis = new FileInputStream(file);
844 ZipInputStream zis = new ZipInputStream(fis);
845 findResourceInZip(zis, prefix + name + "#");
846 fis.close();
847 } else {
848 if (showAll) {
849 FileInputStream fis = new FileInputStream(file);
850 callback.postProcess(prefix + name, file.length(), file.lastModified(), fis, null);
851 fis.close();
852 }
853 }
854 if (abort) { break; }
855 }
856 }
857
858 currentArchiveDepth--;
859 }
860
861
862
863
864
865
866
867 public void ignorableException(String message, Exception e) throws ZipException {
868 if (ignoreErrors) {
869 logger.error(message, e);
870 } else {
871 throw (ZipException) new ZipException(message).initCause(e);
872 }
873 }
874
875
876
877
878
879
880
881
882
883
884
885
886 public void findResourceInZip(ZipInputStream zipInputStream, String prefix) throws IOException {
887 if (maxArchiveDepth!=-1 && currentArchiveDepth>=maxArchiveDepth) {
888 return;
889 }
890
891 currentArchiveDepth++;
892
893
894 ZipEntry zipEntry = null;
895 try {
896 zipEntry = zipInputStream.getNextEntry();
897 } catch (EOFException oefe) {
898 ignorableException("Error retrieving first entry in zip '" + prefix.substring(0, prefix.length()-1) + "'", oefe);
899 currentArchiveDepth--;
900 return;
901 } catch (ZipException ze) {
902 ignorableException("Error retrieving first entry in zip '" + prefix.substring(0, prefix.length()-1) + "'", ze);
903 currentArchiveDepth--;
904 return;
905 } catch (IllegalArgumentException iae) {
906
907 ignorableException("Error retrieving first entry in zip '" + prefix.substring(0, prefix.length()-1) + "'", iae);
908 currentArchiveDepth--;
909 return;
910 }
911 while (zipEntry != null) {
912 String name = zipEntry.getName();
913 String shortName = name;
914
915
916
917 while (shortName.endsWith("/")) { shortName = shortName.substring(0, shortName.length() - 1); }
918 while (shortName.endsWith("\\")) { shortName = shortName.substring(0, shortName.length() - 1); }
919 if (shortName.indexOf('/')!=-1) { shortName = shortName.substring(shortName.lastIndexOf('/') + 1); }
920 if (shortName.indexOf('\\')!=-1) { shortName = shortName.substring(shortName.lastIndexOf('\\') + 1); }
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939 InputStream inputStreamToProcess = zipInputStream;
940
941
942 ResourceFinderCallbackResult rfcbResult = null;
943 if (isArchive(name)) {
944 try {
945 if (matches(shortName) || showAll) {
946 rfcbResult = callback.preProcess(prefix + name, zipEntry.getSize(), zipEntry.getTime(), inputStreamToProcess);
947 if (rfcbResult!=null && rfcbResult.replaceInputStream!=null) {
948 inputStreamToProcess = rfcbResult.replaceInputStream;
949 }
950 if (rfcbResult!=null && rfcbResult.abort) {
951 this.abort = true; break;
952 }
953 }
954 ZipInputStream zis = new ZipInputStream(inputStreamToProcess);
955 findResourceInZip(zis, prefix + name + "#");
956 zipInputStream.closeEntry();
957 } catch (EOFException eofe) {
958
959 ignorableException("Error reading zip '" + prefix + name + "'", eofe);
960 } catch (ZipException ze) {
961 ignorableException("Error reading zip '" + prefix + name + "'", ze);
962 }
963 }
964 if (matches(shortName) || showAll) {
965 rfcbResult = callback.postProcess(prefix + name, zipEntry.getSize(), zipEntry.getTime(), inputStreamToProcess, rfcbResult);
966 if (rfcbResult!=null && rfcbResult.abort) {
967 this.abort = true; break;
968 }
969 }
970
971 try {
972 zipEntry = zipInputStream.getNextEntry();
973 } catch (EOFException oefe) {
974 ignorableException("Error retrieving next entry in zip '" + prefix.substring(0, prefix.length()-1) + "'; after '"+ name + "'", oefe);
975 break;
976 } catch (ZipException ze) {
977 ignorableException("Error retrieving next entry in zip '" + prefix.substring(0, prefix.length()-1) + "'; after '"+ name + "'", ze);
978 break;
979 } catch (IllegalArgumentException iae) {
980
981 ignorableException("Error retrieving next entry in zip '" + prefix.substring(0, prefix.length()-1) + "'; after '"+ name + "'", iae);
982 break;
983 } catch (IOException ioe) {
984
985
986
987 if (ioe.getMessage().contains("Push back buffer")) {
988 ignorableException("Error retrieving next entry in zip '" + prefix.substring(0, prefix.length()-1) + "'; after '"+ name + "'", ioe);
989 break;
990 } else {
991 currentArchiveDepth--;
992 throw ioe;
993 }
994 }
995 if (abort) { break ; }
996 }
997 currentArchiveDepth--;
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009 public static InputStream getResourceStream(String resourceName) throws IOException {
1010 int pos = resourceName.indexOf("#");
1011 if (pos == -1) {
1012 return new FileInputStream(resourceName);
1013 } else {
1014 String filename = resourceName.substring(0, pos);
1015 String component = resourceName.substring(pos + 1);
1016 ZipInputStream zis = new ZipInputStream(new FileInputStream(filename));
1017 return getResourceComponent(zis, component, resourceName);
1018 }
1019 }
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031 public static InputStream getResourceComponent(ZipInputStream zipInputStream, String component, String fullResource) throws IOException {
1032 int pos = component.indexOf("#");
1033 String filename = component;
1034 String subComponent = null;
1035 if (pos != -1) {
1036 filename = component.substring(0, pos);
1037 subComponent = component.substring(pos + 1);
1038 }
1039 ZipEntry zipEntry = zipInputStream.getNextEntry();
1040 while (zipEntry != null) {
1041 if (zipEntry.getName().equals(filename)) {
1042 if (subComponent == null) {
1043 return zipInputStream;
1044 } else {
1045 return getResourceComponent(new ZipInputStream(zipInputStream), subComponent, fullResource);
1046 }
1047 }
1048 zipEntry = zipInputStream.getNextEntry();
1049 }
1050 throw new FileNotFoundException("Could not find component '" + filename + "' in '" + fullResource + "'");
1051 }
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 public void setMaxArchiveDepth(long maxArchiveDepth) {
1065 this.maxArchiveDepth = maxArchiveDepth;
1066 }
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082 public void setMaxFolderDepth(long maxFolderDepth) {
1083 this.maxFolderDepth = maxFolderDepth;
1084 }
1085
1086
1087 public static String usage() {
1088 return
1089 "Usage: \n" +
1090 " java " + ResourceFinder.class.getName() + " [options] searchTerm\n" +
1091 "or\n" +
1092 " java " + ResourceFinder.class.getName() + " [options] -a\n" +
1093 "where [options] are:\n" +
1094 " -h -? displays this helptext\n" +
1095 " -f follow symlinks\n" +
1096 " -a show all resources found (i.e. do not use searchTerm)\n" +
1097 "\n" +
1098 "Search criteria:\n" +
1099 " -i case-insensitive match\n" +
1100 " -sc if present, searchTerm matches within filename (default)\n" +
1101 " -ss if present, searchTerm matches start of filename\n" +
1102 " -se if present, searchTerm matches exact filename\n" +
1103 " -sr if present, searchTerm matches filename as a regular expression\n" +
1104 " -mf n max filesystem folder depth (0 = do not descend into subfolders)\n" +
1105 " -ma n max archive depth (0 = do not descend into archives)\n" +
1106 " -x if present, will attempt to recover if errors occur reading archives\n"+
1107 " (errors sent to stderr)\n" +
1108 "\n" +
1109 "Action when resource found:\n" +
1110 " -v verbose; display filenames with file sizes and timestamps\n" +
1111 " -vv display MD5/SHA1 hashes of resources (NB: modifies display order)\n" +
1112 " -d n dump/display the contents of the n'th resource found\n" +
1113 " -d all dump the name and contents of all resources found\n" +
1114 " -d names dump just the names of all resources found (default)\n" +
1115 " -d n1,n2... dump the name and contents of the n1'th, n2'nd etc... resources found\n" +
1116 " -dm n|all as per -d, but performs manifest unmangling on resource (fixes linewraps)\n" +
1117 " -dj n|all as per -d, but performs class decompiling (requires jad to be in PATH)\n" +
1118 " -c text search for text in contents of resource (uses UTF-8 encoding)\n" +
1119 " -ci text case-insensitive search for text in contents of resources\n" +
1120 "\n" +
1121 "* A maximum of one -d switch should be present\n" +
1122 "* The -d and -c switches are mutually exclusive\n";
1123 }
1124
1125
1126
1127
1128
1129
1130
1131 public static void main(String args[]) throws IOException {
1132 String searchTerm;
1133 String searchContents = null;
1134 String dumpResource = "";
1135 int dumpType = DisplayResourceFinderCallback.DUMP_NAMES;
1136 List<Integer> dumpResourceList = null;
1137 int argIndex = 0;
1138 int matchType = MATCHTYPE_CONTAINS;
1139 long maxArchiveDepth = -1;
1140 long maxFolderDepth = -1;
1141 boolean followSymlinks = false;
1142 boolean verbose = false;
1143 boolean showHashes = false;
1144 boolean showAll = false;
1145 boolean manifests = false;
1146 boolean decompile = false;
1147 boolean ignoreCase = false;
1148 boolean ignoreErrors = false;
1149 boolean searchContentsIgnoreCase = false;
1150
1151 if (args.length < 1) {
1152 System.out.println(usage());
1153 throw new IllegalArgumentException("Expected resource search term or options");
1154 }
1155 while (argIndex < args.length && args[argIndex].startsWith("-")) {
1156 if (args[argIndex].startsWith("-d")) {
1157 if (args[argIndex].equals("-dm")) { manifests = true; }
1158 if (args[argIndex].equals("-dj")) { decompile = true; }
1159
1160 dumpResource = args[argIndex + 1];
1161 if (dumpResource.equals("all")) {
1162 dumpType = DisplayResourceFinderCallback.DUMP_NAMES_AND_RESOURCES;
1163 } else if (dumpResource.equals("names")) {
1164 dumpType = DisplayResourceFinderCallback.DUMP_NAMES;
1165 } else {
1166 dumpResourceList = new ArrayList<Integer>();
1167 String[] resources = dumpResource.split(",");
1168 for (String resource : resources) {
1169 try {
1170 dumpResourceList.add(new Integer(resource));
1171 } catch (NumberFormatException nfe) {
1172
1173 throw new IllegalArgumentException("Expected numeric resource id (found '" + dumpResource + "')");
1174 }
1175 }
1176 if (dumpResourceList.size() == 1) {
1177 dumpType = DisplayResourceFinderCallback.DUMP_RESOURCES;
1178 } else {
1179 dumpType = DisplayResourceFinderCallback.DUMP_NAMES_AND_RESOURCES;
1180 }
1181 }
1182
1183 argIndex += 2;
1184
1185 } else if (args[argIndex].equals("-mf")) {
1186 maxFolderDepth = Long.parseLong(args[argIndex + 1]);
1187 argIndex += 2;
1188 } else if (args[argIndex].equals("-ma")) {
1189 maxArchiveDepth = Long.parseLong(args[argIndex + 1]);
1190 argIndex += 2;
1191 } else if (args[argIndex].equals("-v")) {
1192 verbose = true;
1193 argIndex ++;
1194 } else if (args[argIndex].equals("-vv")) {
1195 verbose = true;
1196 showHashes = true;
1197 argIndex ++;
1198 } else if (args[argIndex].equals("-f")) {
1199 followSymlinks = true;
1200 argIndex ++;
1201 } else if (args[argIndex].equals("-a")) {
1202 showAll = true;
1203 argIndex ++;
1204 } else if (args[argIndex].equals("-sc")) {
1205 matchType = MATCHTYPE_CONTAINS;
1206 argIndex ++;
1207 } else if (args[argIndex].equals("-ss")) {
1208 matchType = MATCHTYPE_STARTSWITH;
1209 argIndex ++;
1210 } else if (args[argIndex].equals("-sr")) {
1211 matchType = MATCHTYPE_REGEX;
1212 argIndex ++;
1213 } else if (args[argIndex].equals("-se")) {
1214 matchType = MATCHTYPE_EXACT;
1215 argIndex ++;
1216 } else if (args[argIndex].equals("-c")) {
1217 searchContents = args[argIndex + 1];
1218
1219 argIndex += 2;
1220 } else if (args[argIndex].equals("-ci")) {
1221 searchContents = args[argIndex + 1];
1222 searchContentsIgnoreCase = true;
1223
1224 argIndex += 2;
1225 } else if (args[argIndex].equals("-i")) {
1226 ignoreCase = true;
1227 argIndex ++;
1228 } else if (args[argIndex].equals("-x")) {
1229 ignoreErrors = true;
1230 argIndex ++;
1231 } else if (args[argIndex].equals("-h") || args[argIndex].equals("-?")) {
1232 System.out.println(usage());
1233 System.exit(0);
1234 } else {
1235 System.out.println(usage());
1236 throw new IllegalArgumentException("Unknown switch '" + args[argIndex] + "' supplied");
1237 }
1238 }
1239
1240 if (showAll) {
1241 searchTerm = "maguffin";
1242 } else {
1243 if (args.length < argIndex + 1) {
1244 System.out.println(usage());
1245 throw new IllegalArgumentException("Expected resource search term");
1246 }
1247 searchTerm = args[argIndex++];
1248 }
1249
1250 ResourceFinderCallback callback;
1251 if (showHashes) {
1252 callback = new HashingResourceFinderCallback(ignoreErrors);
1253 } else {
1254 callback = new DisplayResourceFinderCallback(dumpType, dumpResourceList, verbose, manifests, decompile,
1255 searchContents, searchContentsIgnoreCase);
1256 }
1257
1258 ResourceFinder resourceFinder = new ResourceFinder(searchTerm, matchType, ignoreCase, new File("."), callback);
1259 resourceFinder.setFollowSymLinks(followSymlinks);
1260 resourceFinder.setShowAll(showAll);
1261 resourceFinder.setIgnoreErrors(ignoreErrors);
1262 if (maxArchiveDepth != -1) { resourceFinder.setMaxArchiveDepth(maxArchiveDepth); }
1263 if (maxFolderDepth != -1) { resourceFinder.setMaxFolderDepth(maxFolderDepth); }
1264
1265 resourceFinder.find();
1266
1267
1268 }
1269 }
1270