/
JarFile.java
736 lines (674 loc) · 24.4 KB
/
JarFile.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
/*
* Copyright (c) 1997, 2011, 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.
*/
package java.util.jar;
import java.io.*;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.*;
import java.util.zip.*;
import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.AccessController;
import java.security.CodeSource;
import sun.security.action.GetPropertyAction;
import sun.security.util.ManifestEntryVerifier;
import sun.misc.SharedSecrets;
/**
* The <code>JarFile</code> class is used to read the contents of a jar file
* from any file that can be opened with <code>java.io.RandomAccessFile</code>.
* It extends the class <code>java.util.zip.ZipFile</code> with support
* for reading an optional <code>Manifest</code> entry. The
* <code>Manifest</code> can be used to specify meta-information about the
* jar file and its entries.
*
* <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
*
* @author David Connelly
* @see Manifest
* @see java.util.zip.ZipFile
* @see java.util.jar.JarEntry
* @since 1.2
*/
public
class JarFile extends ZipFile {
private SoftReference<Manifest> manRef;
private JarEntry manEntry;
private JarVerifier jv;
private boolean jvInitialized;
private boolean verify;
private boolean computedHasClassPathAttribute;
private boolean hasClassPathAttribute;
// Set up JavaUtilJarAccess in SharedSecrets
static {
SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
}
/**
* The JAR manifest file name.
*/
public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
/**
* Creates a new <code>JarFile</code> to read from the specified
* file <code>name</code>. The <code>JarFile</code> will be verified if
* it is signed.
* @param name the name of the jar file to be opened for reading
* @throws IOException if an I/O error has occurred
* @throws SecurityException if access to the file is denied
* by the SecurityManager
*/
public JarFile(String name) throws IOException {
this(new File(name), true, ZipFile.OPEN_READ);
}
/**
* Creates a new <code>JarFile</code> to read from the specified
* file <code>name</code>.
* @param name the name of the jar file to be opened for reading
* @param verify whether or not to verify the jar file if
* it is signed.
* @throws IOException if an I/O error has occurred
* @throws SecurityException if access to the file is denied
* by the SecurityManager
*/
public JarFile(String name, boolean verify) throws IOException {
this(new File(name), verify, ZipFile.OPEN_READ);
}
/**
* Creates a new <code>JarFile</code> to read from the specified
* <code>File</code> object. The <code>JarFile</code> will be verified if
* it is signed.
* @param file the jar file to be opened for reading
* @throws IOException if an I/O error has occurred
* @throws SecurityException if access to the file is denied
* by the SecurityManager
*/
public JarFile(File file) throws IOException {
this(file, true, ZipFile.OPEN_READ);
}
/**
* Creates a new <code>JarFile</code> to read from the specified
* <code>File</code> object.
* @param file the jar file to be opened for reading
* @param verify whether or not to verify the jar file if
* it is signed.
* @throws IOException if an I/O error has occurred
* @throws SecurityException if access to the file is denied
* by the SecurityManager.
*/
public JarFile(File file, boolean verify) throws IOException {
this(file, verify, ZipFile.OPEN_READ);
}
/**
* Creates a new <code>JarFile</code> to read from the specified
* <code>File</code> object in the specified mode. The mode argument
* must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
*
* @param file the jar file to be opened for reading
* @param verify whether or not to verify the jar file if
* it is signed.
* @param mode the mode in which the file is to be opened
* @throws IOException if an I/O error has occurred
* @throws IllegalArgumentException
* if the <tt>mode</tt> argument is invalid
* @throws SecurityException if access to the file is denied
* by the SecurityManager
* @since 1.3
*/
public JarFile(File file, boolean verify, int mode) throws IOException {
super(file, mode);
this.verify = verify;
}
/**
* Returns the jar file manifest, or <code>null</code> if none.
*
* @return the jar file manifest, or <code>null</code> if none
*
* @throws IllegalStateException
* may be thrown if the jar file has been closed
*/
public Manifest getManifest() throws IOException {
return getManifestFromReference();
}
private Manifest getManifestFromReference() throws IOException {
Manifest man = manRef != null ? manRef.get() : null;
if (man == null) {
JarEntry manEntry = getManEntry();
// If found then load the manifest
if (manEntry != null) {
if (verify) {
byte[] b = getBytes(manEntry);
man = new Manifest(new ByteArrayInputStream(b));
if (!jvInitialized) {
jv = new JarVerifier(b);
}
} else {
man = new Manifest(super.getInputStream(manEntry));
}
manRef = new SoftReference(man);
}
}
return man;
}
private native String[] getMetaInfEntryNames();
/**
* Returns the <code>JarEntry</code> for the given entry name or
* <code>null</code> if not found.
*
* @param name the jar file entry name
* @return the <code>JarEntry</code> for the given entry name or
* <code>null</code> if not found.
*
* @throws IllegalStateException
* may be thrown if the jar file has been closed
*
* @see java.util.jar.JarEntry
*/
public JarEntry getJarEntry(String name) {
return (JarEntry)getEntry(name);
}
/**
* Returns the <code>ZipEntry</code> for the given entry name or
* <code>null</code> if not found.
*
* @param name the jar file entry name
* @return the <code>ZipEntry</code> for the given entry name or
* <code>null</code> if not found
*
* @throws IllegalStateException
* may be thrown if the jar file has been closed
*
* @see java.util.zip.ZipEntry
*/
public ZipEntry getEntry(String name) {
ZipEntry ze = super.getEntry(name);
if (ze != null) {
return new JarFileEntry(ze);
}
return null;
}
/**
* Returns an enumeration of the zip file entries.
*/
public Enumeration<JarEntry> entries() {
final Enumeration enum_ = super.entries();
return new Enumeration<JarEntry>() {
public boolean hasMoreElements() {
return enum_.hasMoreElements();
}
public JarFileEntry nextElement() {
ZipEntry ze = (ZipEntry)enum_.nextElement();
return new JarFileEntry(ze);
}
};
}
private class JarFileEntry extends JarEntry {
JarFileEntry(ZipEntry ze) {
super(ze);
}
public Attributes getAttributes() throws IOException {
Manifest man = JarFile.this.getManifest();
if (man != null) {
return man.getAttributes(getName());
} else {
return null;
}
}
public Certificate[] getCertificates() {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (certs == null && jv != null) {
certs = jv.getCerts(JarFile.this, this);
}
return certs == null ? null : certs.clone();
}
public CodeSigner[] getCodeSigners() {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (signers == null && jv != null) {
signers = jv.getCodeSigners(JarFile.this, this);
}
return signers == null ? null : signers.clone();
}
}
/*
* Ensures that the JarVerifier has been created if one is
* necessary (i.e., the jar appears to be signed.) This is done as
* a quick check to avoid processing of the manifest for unsigned
* jars.
*/
private void maybeInstantiateVerifier() throws IOException {
if (jv != null) {
return;
}
if (verify) {
String[] names = getMetaInfEntryNames();
if (names != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i].toUpperCase(Locale.ENGLISH);
if (name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.endsWith(".SF")) {
// Assume since we found a signature-related file
// that the jar is signed and that we therefore
// need a JarVerifier and Manifest
getManifest();
return;
}
}
}
// No signature-related files; don't instantiate a
// verifier
verify = false;
}
}
/*
* Initializes the verifier object by reading all the manifest
* entries and passing them to the verifier.
*/
private void initializeVerifier() {
ManifestEntryVerifier mev = null;
// Verify "META-INF/" entries...
try {
String[] names = getMetaInfEntryNames();
if (names != null) {
for (int i = 0; i < names.length; i++) {
JarEntry e = getJarEntry(names[i]);
if (!e.isDirectory()) {
if (mev == null) {
mev = new ManifestEntryVerifier
(getManifestFromReference());
}
byte[] b = getBytes(e);
if (b != null && b.length > 0) {
jv.beginEntry(e, mev);
jv.update(b.length, b, 0, b.length, mev);
jv.update(-1, null, 0, 0, mev);
}
}
}
}
} catch (IOException ex) {
// if we had an error parsing any blocks, just
// treat the jar file as being unsigned
jv = null;
verify = false;
}
// if after initializing the verifier we have nothing
// signed, we null it out.
if (jv != null) {
jv.doneWithMeta();
if (JarVerifier.debug != null) {
JarVerifier.debug.println("done with meta!");
}
if (jv.nothingToVerify()) {
if (JarVerifier.debug != null) {
JarVerifier.debug.println("nothing to verify!");
}
jv = null;
verify = false;
}
}
}
/*
* Reads all the bytes for a given entry. Used to process the
* META-INF files.
*/
private byte[] getBytes(ZipEntry ze) throws IOException {
byte[] b = new byte[(int)ze.getSize()];
try (DataInputStream is = new DataInputStream(super.getInputStream(ze))) {
is.readFully(b, 0, b.length);
}
return b;
}
/**
* Returns an input stream for reading the contents of the specified
* zip file entry.
* @param ze the zip file entry
* @return an input stream for reading the contents of the specified
* zip file entry
* @throws ZipException if a zip file format error has occurred
* @throws IOException if an I/O error has occurred
* @throws SecurityException if any of the jar file entries
* are incorrectly signed.
* @throws IllegalStateException
* may be thrown if the jar file has been closed
*/
public synchronized InputStream getInputStream(ZipEntry ze)
throws IOException
{
maybeInstantiateVerifier();
if (jv == null) {
return super.getInputStream(ze);
}
if (!jvInitialized) {
initializeVerifier();
jvInitialized = true;
// could be set to null after a call to
// initializeVerifier if we have nothing to
// verify
if (jv == null)
return super.getInputStream(ze);
}
// wrap a verifier stream around the real stream
return new JarVerifier.VerifierStream(
getManifestFromReference(),
ze instanceof JarFileEntry ?
(JarEntry) ze : getJarEntry(ze.getName()),
super.getInputStream(ze),
jv);
}
// Statics for hand-coded Boyer-Moore search in hasClassPathAttribute()
// The bad character shift for "class-path"
private static int[] lastOcc;
// The good suffix shift for "class-path"
private static int[] optoSft;
// Initialize the shift arrays to search for "class-path"
private static char[] src = {'c','l','a','s','s','-','p','a','t','h'};
static {
lastOcc = new int[128];
optoSft = new int[10];
lastOcc[(int)'c']=1;
lastOcc[(int)'l']=2;
lastOcc[(int)'s']=5;
lastOcc[(int)'-']=6;
lastOcc[(int)'p']=7;
lastOcc[(int)'a']=8;
lastOcc[(int)'t']=9;
lastOcc[(int)'h']=10;
for (int i=0; i<9; i++)
optoSft[i]=10;
optoSft[9]=1;
}
private JarEntry getManEntry() {
if (manEntry == null) {
// First look up manifest entry using standard name
manEntry = getJarEntry(MANIFEST_NAME);
if (manEntry == null) {
// If not found, then iterate through all the "META-INF/"
// entries to find a match.
String[] names = getMetaInfEntryNames();
if (names != null) {
for (int i = 0; i < names.length; i++) {
if (MANIFEST_NAME.equals(
names[i].toUpperCase(Locale.ENGLISH))) {
manEntry = getJarEntry(names[i]);
break;
}
}
}
}
}
return manEntry;
}
// Returns true iff this jar file has a manifest with a class path
// attribute. Returns false if there is no manifest or the manifest
// does not contain a "Class-Path" attribute. Currently exported to
// core libraries via sun.misc.SharedSecrets.
boolean hasClassPathAttribute() throws IOException {
if (computedHasClassPathAttribute) {
return hasClassPathAttribute;
}
hasClassPathAttribute = false;
if (!isKnownToNotHaveClassPathAttribute()) {
JarEntry manEntry = getManEntry();
if (manEntry != null) {
byte[] b = new byte[(int)manEntry.getSize()];
try (DataInputStream dis = new DataInputStream(
super.getInputStream(manEntry))) {
dis.readFully(b, 0, b.length);
}
int last = b.length - src.length;
int i = 0;
next:
while (i<=last) {
for (int j=9; j>=0; j--) {
char c = (char) b[i+j];
c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
if (c != src[j]) {
i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
continue next;
}
}
hasClassPathAttribute = true;
break;
}
}
}
computedHasClassPathAttribute = true;
return hasClassPathAttribute;
}
private static String javaHome;
private static String[] jarNames;
private boolean isKnownToNotHaveClassPathAttribute() {
// Optimize away even scanning of manifest for jar files we
// deliver which don't have a class-path attribute. If one of
// these jars is changed to include such an attribute this code
// must be changed.
if (javaHome == null) {
javaHome = AccessController.doPrivileged(
new GetPropertyAction("java.home"));
}
if (jarNames == null) {
String[] names = new String[10];
String fileSep = File.separator;
int i = 0;
names[i++] = fileSep + "rt.jar";
names[i++] = fileSep + "sunrsasign.jar";
names[i++] = fileSep + "jsse.jar";
names[i++] = fileSep + "jce.jar";
names[i++] = fileSep + "charsets.jar";
names[i++] = fileSep + "dnsns.jar";
names[i++] = fileSep + "ldapsec.jar";
names[i++] = fileSep + "localedata.jar";
names[i++] = fileSep + "sunjce_provider.jar";
names[i++] = fileSep + "sunpkcs11.jar";
jarNames = names;
}
String name = getName();
String localJavaHome = javaHome;
if (name.startsWith(localJavaHome)) {
String[] names = jarNames;
for (int i = 0; i < names.length; i++) {
if (name.endsWith(names[i])) {
return true;
}
}
}
return false;
}
private synchronized void ensureInitialization() {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (jv != null && !jvInitialized) {
initializeVerifier();
jvInitialized = true;
}
}
JarEntry newEntry(ZipEntry ze) {
return new JarFileEntry(ze);
}
Enumeration<String> entryNames(CodeSource[] cs) {
ensureInitialization();
if (jv != null) {
return jv.entryNames(this, cs);
}
/*
* JAR file has no signed content. Is there a non-signing
* code source?
*/
boolean includeUnsigned = false;
for (int i = 0; i < cs.length; i++) {
if (cs[i].getCodeSigners() == null) {
includeUnsigned = true;
break;
}
}
if (includeUnsigned) {
return unsignedEntryNames();
} else {
return new Enumeration<String>() {
public boolean hasMoreElements() {
return false;
}
public String nextElement() {
throw new NoSuchElementException();
}
};
}
}
/**
* Returns an enumeration of the zip file entries
* excluding internal JAR mechanism entries and including
* signed entries missing from the ZIP directory.
*/
Enumeration<JarEntry> entries2() {
ensureInitialization();
if (jv != null) {
return jv.entries2(this, super.entries());
}
// screen out entries which are never signed
final Enumeration enum_ = super.entries();
return new Enumeration<JarEntry>() {
ZipEntry entry;
public boolean hasMoreElements() {
if (entry != null) {
return true;
}
while (enum_.hasMoreElements()) {
ZipEntry ze = (ZipEntry) enum_.nextElement();
if (JarVerifier.isSigningRelated(ze.getName())) {
continue;
}
entry = ze;
return true;
}
return false;
}
public JarFileEntry nextElement() {
if (hasMoreElements()) {
ZipEntry ze = entry;
entry = null;
return new JarFileEntry(ze);
}
throw new NoSuchElementException();
}
};
}
CodeSource[] getCodeSources(URL url) {
ensureInitialization();
if (jv != null) {
return jv.getCodeSources(this, url);
}
/*
* JAR file has no signed content. Is there a non-signing
* code source?
*/
Enumeration unsigned = unsignedEntryNames();
if (unsigned.hasMoreElements()) {
return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
} else {
return null;
}
}
private Enumeration<String> unsignedEntryNames() {
final Enumeration entries = entries();
return new Enumeration<String>() {
String name;
/*
* Grab entries from ZIP directory but screen out
* metadata.
*/
public boolean hasMoreElements() {
if (name != null) {
return true;
}
while (entries.hasMoreElements()) {
String value;
ZipEntry e = (ZipEntry) entries.nextElement();
value = e.getName();
if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
continue;
}
name = value;
return true;
}
return false;
}
public String nextElement() {
if (hasMoreElements()) {
String value = name;
name = null;
return value;
}
throw new NoSuchElementException();
}
};
}
CodeSource getCodeSource(URL url, String name) {
ensureInitialization();
if (jv != null) {
if (jv.eagerValidation) {
CodeSource cs = null;
JarEntry je = getJarEntry(name);
if (je != null) {
cs = jv.getCodeSource(url, this, je);
} else {
cs = jv.getCodeSource(url, name);
}
return cs;
} else {
return jv.getCodeSource(url, name);
}
}
return JarVerifier.getUnsignedCS(url);
}
void setEagerValidation(boolean eager) {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (jv != null) {
jv.setEagerValidation(eager);
}
}
List getManifestDigests() {
ensureInitialization();
if (jv != null) {
return jv.getManifestDigests();
}
return new ArrayList();
}
}