Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
67766 views
1
/*
2
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package jdk.internal.loader;
27
28
import java.io.Closeable;
29
import java.io.File;
30
import java.io.FileInputStream;
31
import java.io.FileNotFoundException;
32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.net.HttpURLConnection;
35
import java.net.JarURLConnection;
36
import java.net.MalformedURLException;
37
import java.net.URI;
38
import java.net.URL;
39
import java.net.URLConnection;
40
import java.net.URLStreamHandler;
41
import java.net.URLStreamHandlerFactory;
42
import java.security.AccessControlContext;
43
import java.security.AccessControlException;
44
import java.security.AccessController;
45
import java.security.CodeSigner;
46
import java.security.Permission;
47
import java.security.PrivilegedActionException;
48
import java.security.PrivilegedExceptionAction;
49
import java.security.cert.Certificate;
50
import java.util.ArrayDeque;
51
import java.util.ArrayList;
52
import java.util.Arrays;
53
import java.util.Collections;
54
import java.util.Enumeration;
55
import java.util.HashMap;
56
import java.util.HashSet;
57
import java.util.LinkedList;
58
import java.util.List;
59
import java.util.NoSuchElementException;
60
import java.util.Properties;
61
import java.util.Set;
62
import java.util.StringTokenizer;
63
import java.util.jar.JarFile;
64
import java.util.zip.CRC32;
65
import java.util.zip.ZipEntry;
66
import java.util.jar.JarEntry;
67
import java.util.jar.Manifest;
68
import java.util.jar.Attributes;
69
import java.util.jar.Attributes.Name;
70
import java.util.zip.ZipFile;
71
72
import jdk.internal.access.JavaNetURLAccess;
73
import jdk.internal.access.JavaUtilZipFileAccess;
74
import jdk.internal.access.SharedSecrets;
75
import jdk.internal.util.jar.InvalidJarIndexError;
76
import jdk.internal.util.jar.JarIndex;
77
import sun.net.util.URLUtil;
78
import sun.net.www.ParseUtil;
79
import sun.security.action.GetPropertyAction;
80
81
/**
82
* This class is used to maintain a search path of URLs for loading classes
83
* and resources from both JAR files and directories.
84
*
85
* @author David Connelly
86
*/
87
public class URLClassPath {
88
private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
89
private static final String JAVA_VERSION;
90
private static final boolean DEBUG;
91
private static final boolean DISABLE_JAR_CHECKING;
92
private static final boolean DISABLE_ACC_CHECKING;
93
private static final boolean DISABLE_CP_URL_CHECK;
94
private static final boolean DEBUG_CP_URL_CHECK;
95
96
static {
97
Properties props = GetPropertyAction.privilegedGetProperties();
98
JAVA_VERSION = props.getProperty("java.version");
99
DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null);
100
String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking");
101
DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;
102
103
p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");
104
DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;
105
106
// This property will be removed in a later release
107
p = props.getProperty("jdk.net.URLClassPath.disableClassPathURLCheck");
108
DISABLE_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;
109
110
// Print a message for each Class-Path entry that is ignored (assuming
111
// the check is not disabled).
112
p = props.getProperty("jdk.net.URLClassPath.showIgnoredClassPathEntries");
113
DEBUG_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;
114
}
115
116
/* The original search path of URLs. */
117
private final ArrayList<URL> path;
118
119
/* The deque of unopened URLs */
120
private final ArrayDeque<URL> unopenedUrls;
121
122
/* The resulting search path of Loaders */
123
private final ArrayList<Loader> loaders = new ArrayList<>();
124
125
/* Map of each URL opened to its corresponding Loader */
126
private final HashMap<String, Loader> lmap = new HashMap<>();
127
128
/* The jar protocol handler to use when creating new URLs */
129
private final URLStreamHandler jarHandler;
130
131
/* Whether this URLClassLoader has been closed yet */
132
private boolean closed = false;
133
134
/* The context to be used when loading classes and resources. If non-null
135
* this is the context that was captured during the creation of the
136
* URLClassLoader. null implies no additional security restrictions. */
137
@SuppressWarnings("removal")
138
private final AccessControlContext acc;
139
140
/**
141
* Creates a new URLClassPath for the given URLs. The URLs will be
142
* searched in the order specified for classes and resources. A URL
143
* ending with a '/' is assumed to refer to a directory. Otherwise,
144
* the URL is assumed to refer to a JAR file.
145
*
146
* @param urls the directory and JAR file URLs to search for classes
147
* and resources
148
* @param factory the URLStreamHandlerFactory to use when creating new URLs
149
* @param acc the context to be used when loading classes and resources, may
150
* be null
151
*/
152
public URLClassPath(URL[] urls,
153
URLStreamHandlerFactory factory,
154
@SuppressWarnings("removal") AccessControlContext acc) {
155
ArrayList<URL> path = new ArrayList<>(urls.length);
156
ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(urls.length);
157
for (URL url : urls) {
158
path.add(url);
159
unopenedUrls.add(url);
160
}
161
this.path = path;
162
this.unopenedUrls = unopenedUrls;
163
164
if (factory != null) {
165
jarHandler = factory.createURLStreamHandler("jar");
166
} else {
167
jarHandler = null;
168
}
169
if (DISABLE_ACC_CHECKING)
170
this.acc = null;
171
else
172
this.acc = acc;
173
}
174
175
public URLClassPath(URL[] urls, @SuppressWarnings("removal") AccessControlContext acc) {
176
this(urls, null, acc);
177
}
178
179
/**
180
* Constructs a URLClassPath from a class path string.
181
*
182
* @param cp the class path string
183
* @param skipEmptyElements indicates if empty elements are ignored or
184
* treated as the current working directory
185
*
186
* @apiNote Used to create the application class path.
187
*/
188
URLClassPath(String cp, boolean skipEmptyElements) {
189
ArrayList<URL> path = new ArrayList<>();
190
if (cp != null) {
191
// map each element of class path to a file URL
192
int off = 0, next;
193
do {
194
next = cp.indexOf(File.pathSeparator, off);
195
String element = (next == -1)
196
? cp.substring(off)
197
: cp.substring(off, next);
198
if (!element.isEmpty() || !skipEmptyElements) {
199
URL url = toFileURL(element);
200
if (url != null) path.add(url);
201
}
202
off = next + 1;
203
} while (next != -1);
204
}
205
206
// can't use ArrayDeque#addAll or new ArrayDeque(Collection);
207
// it's too early in the bootstrap to trigger use of lambdas
208
int size = path.size();
209
ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(size);
210
for (int i = 0; i < size; i++)
211
unopenedUrls.add(path.get(i));
212
213
this.unopenedUrls = unopenedUrls;
214
this.path = path;
215
this.jarHandler = null;
216
this.acc = null;
217
}
218
219
public synchronized List<IOException> closeLoaders() {
220
if (closed) {
221
return Collections.emptyList();
222
}
223
List<IOException> result = new LinkedList<>();
224
for (Loader loader : loaders) {
225
try {
226
loader.close();
227
} catch (IOException e) {
228
result.add(e);
229
}
230
}
231
closed = true;
232
return result;
233
}
234
235
/**
236
* Appends the specified URL to the search path of directory and JAR
237
* file URLs from which to load classes and resources.
238
* <p>
239
* If the URL specified is null or is already in the list of
240
* URLs, then invoking this method has no effect.
241
*/
242
public synchronized void addURL(URL url) {
243
if (closed || url == null)
244
return;
245
synchronized (unopenedUrls) {
246
if (! path.contains(url)) {
247
unopenedUrls.addLast(url);
248
path.add(url);
249
}
250
}
251
}
252
253
/**
254
* Appends the specified file path as a file URL to the search path.
255
*/
256
public void addFile(String s) {
257
URL url = toFileURL(s);
258
if (url != null) {
259
addURL(url);
260
}
261
}
262
263
/**
264
* Returns a file URL for the given file path.
265
*/
266
private static URL toFileURL(String s) {
267
try {
268
File f = new File(s).getCanonicalFile();
269
return ParseUtil.fileToEncodedURL(f);
270
} catch (IOException e) {
271
return null;
272
}
273
}
274
275
/**
276
* Returns the original search path of URLs.
277
*/
278
public URL[] getURLs() {
279
synchronized (unopenedUrls) {
280
return path.toArray(new URL[0]);
281
}
282
}
283
284
/**
285
* Finds the resource with the specified name on the URL search path
286
* or null if not found or security check fails.
287
*
288
* @param name the name of the resource
289
* @param check whether to perform a security check
290
* @return a {@code URL} for the resource, or {@code null}
291
* if the resource could not be found.
292
*/
293
public URL findResource(String name, boolean check) {
294
Loader loader;
295
for (int i = 0; (loader = getLoader(i)) != null; i++) {
296
URL url = loader.findResource(name, check);
297
if (url != null) {
298
return url;
299
}
300
}
301
return null;
302
}
303
304
/**
305
* Finds the first Resource on the URL search path which has the specified
306
* name. Returns null if no Resource could be found.
307
*
308
* @param name the name of the Resource
309
* @param check whether to perform a security check
310
* @return the Resource, or null if not found
311
*/
312
public Resource getResource(String name, boolean check) {
313
if (DEBUG) {
314
System.err.println("URLClassPath.getResource(\"" + name + "\")");
315
}
316
317
Loader loader;
318
for (int i = 0; (loader = getLoader(i)) != null; i++) {
319
Resource res = loader.getResource(name, check);
320
if (res != null) {
321
return res;
322
}
323
}
324
return null;
325
}
326
327
/**
328
* Finds all resources on the URL search path with the given name.
329
* Returns an enumeration of the URL objects.
330
*
331
* @param name the resource name
332
* @return an Enumeration of all the urls having the specified name
333
*/
334
public Enumeration<URL> findResources(final String name,
335
final boolean check) {
336
return new Enumeration<>() {
337
private int index = 0;
338
private URL url = null;
339
340
private boolean next() {
341
if (url != null) {
342
return true;
343
} else {
344
Loader loader;
345
while ((loader = getLoader(index++)) != null) {
346
url = loader.findResource(name, check);
347
if (url != null) {
348
return true;
349
}
350
}
351
return false;
352
}
353
}
354
355
public boolean hasMoreElements() {
356
return next();
357
}
358
359
public URL nextElement() {
360
if (!next()) {
361
throw new NoSuchElementException();
362
}
363
URL u = url;
364
url = null;
365
return u;
366
}
367
};
368
}
369
370
public Resource getResource(String name) {
371
return getResource(name, true);
372
}
373
374
/**
375
* Finds all resources on the URL search path with the given name.
376
* Returns an enumeration of the Resource objects.
377
*
378
* @param name the resource name
379
* @return an Enumeration of all the resources having the specified name
380
*/
381
public Enumeration<Resource> getResources(final String name,
382
final boolean check) {
383
return new Enumeration<>() {
384
private int index = 0;
385
private Resource res = null;
386
387
private boolean next() {
388
if (res != null) {
389
return true;
390
} else {
391
Loader loader;
392
while ((loader = getLoader(index++)) != null) {
393
res = loader.getResource(name, check);
394
if (res != null) {
395
return true;
396
}
397
}
398
return false;
399
}
400
}
401
402
public boolean hasMoreElements() {
403
return next();
404
}
405
406
public Resource nextElement() {
407
if (!next()) {
408
throw new NoSuchElementException();
409
}
410
Resource r = res;
411
res = null;
412
return r;
413
}
414
};
415
}
416
417
public Enumeration<Resource> getResources(final String name) {
418
return getResources(name, true);
419
}
420
421
/*
422
* Returns the Loader at the specified position in the URL search
423
* path. The URLs are opened and expanded as needed. Returns null
424
* if the specified index is out of range.
425
*/
426
private synchronized Loader getLoader(int index) {
427
if (closed) {
428
return null;
429
}
430
// Expand URL search path until the request can be satisfied
431
// or unopenedUrls is exhausted.
432
while (loaders.size() < index + 1) {
433
final URL url;
434
synchronized (unopenedUrls) {
435
url = unopenedUrls.pollFirst();
436
if (url == null)
437
return null;
438
}
439
// Skip this URL if it already has a Loader. (Loader
440
// may be null in the case where URL has not been opened
441
// but is referenced by a JAR index.)
442
String urlNoFragString = URLUtil.urlNoFragString(url);
443
if (lmap.containsKey(urlNoFragString)) {
444
continue;
445
}
446
// Otherwise, create a new Loader for the URL.
447
Loader loader;
448
try {
449
loader = getLoader(url);
450
// If the loader defines a local class path then add the
451
// URLs as the next URLs to be opened.
452
URL[] urls = loader.getClassPath();
453
if (urls != null) {
454
push(urls);
455
}
456
} catch (IOException e) {
457
// Silently ignore for now...
458
continue;
459
} catch (SecurityException se) {
460
// Always silently ignore. The context, if there is one, that
461
// this URLClassPath was given during construction will never
462
// have permission to access the URL.
463
if (DEBUG) {
464
System.err.println("Failed to access " + url + ", " + se );
465
}
466
continue;
467
}
468
// Finally, add the Loader to the search path.
469
loaders.add(loader);
470
lmap.put(urlNoFragString, loader);
471
}
472
return loaders.get(index);
473
}
474
475
/*
476
* Returns the Loader for the specified base URL.
477
*/
478
@SuppressWarnings("removal")
479
private Loader getLoader(final URL url) throws IOException {
480
try {
481
return AccessController.doPrivileged(
482
new PrivilegedExceptionAction<>() {
483
public Loader run() throws IOException {
484
String protocol = url.getProtocol(); // lower cased in URL
485
String file = url.getFile();
486
if (file != null && file.endsWith("/")) {
487
if ("file".equals(protocol)) {
488
return new FileLoader(url);
489
} else if ("jar".equals(protocol) &&
490
isDefaultJarHandler(url) &&
491
file.endsWith("!/")) {
492
// extract the nested URL
493
URL nestedUrl = new URL(file.substring(0, file.length() - 2));
494
return new JarLoader(nestedUrl, jarHandler, lmap, acc);
495
} else {
496
return new Loader(url);
497
}
498
} else {
499
return new JarLoader(url, jarHandler, lmap, acc);
500
}
501
}
502
}, acc);
503
} catch (PrivilegedActionException pae) {
504
throw (IOException)pae.getException();
505
}
506
}
507
508
private static final JavaNetURLAccess JNUA
509
= SharedSecrets.getJavaNetURLAccess();
510
511
private static boolean isDefaultJarHandler(URL u) {
512
URLStreamHandler h = JNUA.getHandler(u);
513
return h instanceof sun.net.www.protocol.jar.Handler;
514
}
515
516
/*
517
* Pushes the specified URLs onto the head of unopened URLs.
518
*/
519
private void push(URL[] urls) {
520
synchronized (unopenedUrls) {
521
for (int i = urls.length - 1; i >= 0; --i) {
522
unopenedUrls.addFirst(urls[i]);
523
}
524
}
525
}
526
527
/*
528
* Checks whether the resource URL should be returned.
529
* Returns null on security check failure.
530
* Called by java.net.URLClassLoader.
531
*/
532
public static URL checkURL(URL url) {
533
if (url != null) {
534
try {
535
check(url);
536
} catch (Exception e) {
537
return null;
538
}
539
}
540
return url;
541
}
542
543
/*
544
* Checks whether the resource URL should be returned.
545
* Throws exception on failure.
546
* Called internally within this file.
547
*/
548
public static void check(URL url) throws IOException {
549
@SuppressWarnings("removal")
550
SecurityManager security = System.getSecurityManager();
551
if (security != null) {
552
URLConnection urlConnection = url.openConnection();
553
Permission perm = urlConnection.getPermission();
554
if (perm != null) {
555
try {
556
security.checkPermission(perm);
557
} catch (SecurityException se) {
558
// fallback to checkRead/checkConnect for pre 1.2
559
// security managers
560
if ((perm instanceof java.io.FilePermission) &&
561
perm.getActions().indexOf("read") != -1) {
562
security.checkRead(perm.getName());
563
} else if ((perm instanceof
564
java.net.SocketPermission) &&
565
perm.getActions().indexOf("connect") != -1) {
566
URL locUrl = url;
567
if (urlConnection instanceof JarURLConnection) {
568
locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
569
}
570
security.checkConnect(locUrl.getHost(),
571
locUrl.getPort());
572
} else {
573
throw se;
574
}
575
}
576
}
577
}
578
}
579
580
/**
581
* Nested class used to represent a loader of resources and classes
582
* from a base URL.
583
*/
584
private static class Loader implements Closeable {
585
private final URL base;
586
private JarFile jarfile; // if this points to a jar file
587
588
/*
589
* Creates a new Loader for the specified URL.
590
*/
591
Loader(URL url) {
592
base = url;
593
}
594
595
/*
596
* Returns the base URL for this Loader.
597
*/
598
URL getBaseURL() {
599
return base;
600
}
601
602
URL findResource(final String name, boolean check) {
603
URL url;
604
try {
605
url = new URL(base, ParseUtil.encodePath(name, false));
606
} catch (MalformedURLException e) {
607
return null;
608
}
609
610
try {
611
if (check) {
612
URLClassPath.check(url);
613
}
614
615
/*
616
* For a HTTP connection we use the HEAD method to
617
* check if the resource exists.
618
*/
619
URLConnection uc = url.openConnection();
620
if (uc instanceof HttpURLConnection) {
621
HttpURLConnection hconn = (HttpURLConnection)uc;
622
hconn.setRequestMethod("HEAD");
623
if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
624
return null;
625
}
626
} else {
627
// our best guess for the other cases
628
uc.setUseCaches(false);
629
InputStream is = uc.getInputStream();
630
is.close();
631
}
632
return url;
633
} catch (Exception e) {
634
return null;
635
}
636
}
637
638
Resource getResource(final String name, boolean check) {
639
final URL url;
640
try {
641
url = new URL(base, ParseUtil.encodePath(name, false));
642
} catch (MalformedURLException e) {
643
return null;
644
}
645
final URLConnection uc;
646
try {
647
if (check) {
648
URLClassPath.check(url);
649
}
650
uc = url.openConnection();
651
652
if (uc instanceof JarURLConnection) {
653
/* Need to remember the jar file so it can be closed
654
* in a hurry.
655
*/
656
JarURLConnection juc = (JarURLConnection)uc;
657
jarfile = JarLoader.checkJar(juc.getJarFile());
658
}
659
660
InputStream in = uc.getInputStream();
661
} catch (Exception e) {
662
return null;
663
}
664
return new Resource() {
665
public String getName() { return name; }
666
public URL getURL() { return url; }
667
public URL getCodeSourceURL() { return base; }
668
public InputStream getInputStream() throws IOException {
669
return uc.getInputStream();
670
}
671
public int getContentLength() throws IOException {
672
return uc.getContentLength();
673
}
674
};
675
}
676
677
/*
678
* Returns the Resource for the specified name, or null if not
679
* found or the caller does not have the permission to get the
680
* resource.
681
*/
682
Resource getResource(final String name) {
683
return getResource(name, true);
684
}
685
686
/*
687
* Closes this loader and release all resources.
688
* Method overridden in sub-classes.
689
*/
690
@Override
691
public void close() throws IOException {
692
if (jarfile != null) {
693
jarfile.close();
694
}
695
}
696
697
/*
698
* Returns the local class path for this loader, or null if none.
699
*/
700
URL[] getClassPath() throws IOException {
701
return null;
702
}
703
}
704
705
/*
706
* Nested class used to represent a Loader of resources from a JAR URL.
707
*/
708
private static class JarLoader extends Loader {
709
private JarFile jar;
710
private final URL csu;
711
private JarIndex index;
712
private URLStreamHandler handler;
713
private final HashMap<String, Loader> lmap;
714
@SuppressWarnings("removal")
715
private final AccessControlContext acc;
716
private boolean closed = false;
717
private static final JavaUtilZipFileAccess zipAccess =
718
SharedSecrets.getJavaUtilZipFileAccess();
719
720
/*
721
* Creates a new JarLoader for the specified URL referring to
722
* a JAR file.
723
*/
724
private JarLoader(URL url, URLStreamHandler jarHandler,
725
HashMap<String, Loader> loaderMap,
726
@SuppressWarnings("removal") AccessControlContext acc)
727
throws IOException
728
{
729
super(new URL("jar", "", -1, url + "!/", jarHandler));
730
csu = url;
731
handler = jarHandler;
732
lmap = loaderMap;
733
this.acc = acc;
734
735
ensureOpen();
736
}
737
738
@Override
739
public void close () throws IOException {
740
// closing is synchronized at higher level
741
if (!closed) {
742
closed = true;
743
// in case not already open.
744
ensureOpen();
745
jar.close();
746
}
747
}
748
749
JarFile getJarFile () {
750
return jar;
751
}
752
753
private boolean isOptimizable(URL url) {
754
return "file".equals(url.getProtocol());
755
}
756
757
@SuppressWarnings("removal")
758
private void ensureOpen() throws IOException {
759
if (jar == null) {
760
try {
761
AccessController.doPrivileged(
762
new PrivilegedExceptionAction<>() {
763
public Void run() throws IOException {
764
if (DEBUG) {
765
System.err.println("Opening " + csu);
766
Thread.dumpStack();
767
}
768
769
jar = getJarFile(csu);
770
index = JarIndex.getJarIndex(jar);
771
if (index != null) {
772
String[] jarfiles = index.getJarFiles();
773
// Add all the dependent URLs to the lmap so that loaders
774
// will not be created for them by URLClassPath.getLoader(int)
775
// if the same URL occurs later on the main class path. We set
776
// Loader to null here to avoid creating a Loader for each
777
// URL until we actually need to try to load something from them.
778
for (int i = 0; i < jarfiles.length; i++) {
779
try {
780
URL jarURL = new URL(csu, jarfiles[i]);
781
// If a non-null loader already exists, leave it alone.
782
String urlNoFragString = URLUtil.urlNoFragString(jarURL);
783
if (!lmap.containsKey(urlNoFragString)) {
784
lmap.put(urlNoFragString, null);
785
}
786
} catch (MalformedURLException e) {
787
continue;
788
}
789
}
790
}
791
return null;
792
}
793
}, acc);
794
} catch (PrivilegedActionException pae) {
795
throw (IOException)pae.getException();
796
}
797
}
798
}
799
800
/* Throws if the given jar file is does not start with the correct LOC */
801
@SuppressWarnings("removal")
802
static JarFile checkJar(JarFile jar) throws IOException {
803
if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
804
&& !zipAccess.startsWithLocHeader(jar)) {
805
IOException x = new IOException("Invalid Jar file");
806
try {
807
jar.close();
808
} catch (IOException ex) {
809
x.addSuppressed(ex);
810
}
811
throw x;
812
}
813
814
return jar;
815
}
816
817
private JarFile getJarFile(URL url) throws IOException {
818
// Optimize case where url refers to a local jar file
819
if (isOptimizable(url)) {
820
FileURLMapper p = new FileURLMapper(url);
821
if (!p.exists()) {
822
throw new FileNotFoundException(p.getPath());
823
}
824
return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,
825
JarFile.runtimeVersion()));
826
}
827
URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();
828
uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
829
JarFile jarFile = ((JarURLConnection)uc).getJarFile();
830
return checkJar(jarFile);
831
}
832
833
/*
834
* Returns the index of this JarLoader if it exists.
835
*/
836
JarIndex getIndex() {
837
try {
838
ensureOpen();
839
} catch (IOException e) {
840
throw new InternalError(e);
841
}
842
return index;
843
}
844
845
/*
846
* Creates the resource and if the check flag is set to true, checks if
847
* is its okay to return the resource.
848
*/
849
Resource checkResource(final String name, boolean check,
850
final JarEntry entry) {
851
852
final URL url;
853
try {
854
String nm;
855
if (jar.isMultiRelease()) {
856
nm = entry.getRealName();
857
} else {
858
nm = name;
859
}
860
url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));
861
if (check) {
862
URLClassPath.check(url);
863
}
864
} catch (MalformedURLException e) {
865
return null;
866
// throw new IllegalArgumentException("name");
867
} catch (IOException e) {
868
return null;
869
} catch (@SuppressWarnings("removal") AccessControlException e) {
870
return null;
871
}
872
873
return new Resource() {
874
private Exception dataError = null;
875
public String getName() { return name; }
876
public URL getURL() { return url; }
877
public URL getCodeSourceURL() { return csu; }
878
public InputStream getInputStream() throws IOException
879
{ return jar.getInputStream(entry); }
880
public int getContentLength()
881
{ return (int)entry.getSize(); }
882
public Manifest getManifest() throws IOException {
883
SharedSecrets.javaUtilJarAccess().ensureInitialization(jar);
884
return jar.getManifest();
885
}
886
public Certificate[] getCertificates()
887
{ return entry.getCertificates(); };
888
public CodeSigner[] getCodeSigners()
889
{ return entry.getCodeSigners(); };
890
public Exception getDataError()
891
{ return dataError; }
892
public byte[] getBytes() throws IOException {
893
byte[] bytes = super.getBytes();
894
CRC32 crc32 = new CRC32();
895
crc32.update(bytes);
896
if (crc32.getValue() != entry.getCrc()) {
897
dataError = new IOException(
898
"CRC error while extracting entry from JAR file");
899
}
900
return bytes;
901
}
902
};
903
}
904
905
906
/*
907
* Returns true iff at least one resource in the jar file has the same
908
* package name as that of the specified resource name.
909
*/
910
boolean validIndex(final String name) {
911
String packageName = name;
912
int pos;
913
if ((pos = name.lastIndexOf('/')) != -1) {
914
packageName = name.substring(0, pos);
915
}
916
917
String entryName;
918
ZipEntry entry;
919
Enumeration<JarEntry> enum_ = jar.entries();
920
while (enum_.hasMoreElements()) {
921
entry = enum_.nextElement();
922
entryName = entry.getName();
923
if ((pos = entryName.lastIndexOf('/')) != -1)
924
entryName = entryName.substring(0, pos);
925
if (entryName.equals(packageName)) {
926
return true;
927
}
928
}
929
return false;
930
}
931
932
/*
933
* Returns the URL for a resource with the specified name
934
*/
935
@Override
936
URL findResource(final String name, boolean check) {
937
Resource rsc = getResource(name, check);
938
if (rsc != null) {
939
return rsc.getURL();
940
}
941
return null;
942
}
943
944
/*
945
* Returns the JAR Resource for the specified name.
946
*/
947
@Override
948
Resource getResource(final String name, boolean check) {
949
try {
950
ensureOpen();
951
} catch (IOException e) {
952
throw new InternalError(e);
953
}
954
final JarEntry entry = jar.getJarEntry(name);
955
if (entry != null)
956
return checkResource(name, check, entry);
957
958
if (index == null)
959
return null;
960
961
HashSet<String> visited = new HashSet<>();
962
return getResource(name, check, visited);
963
}
964
965
/*
966
* Version of getResource() that tracks the jar files that have been
967
* visited by linking through the index files. This helper method uses
968
* a HashSet to store the URLs of jar files that have been searched and
969
* uses it to avoid going into an infinite loop, looking for a
970
* non-existent resource.
971
*/
972
@SuppressWarnings("removal")
973
Resource getResource(final String name, boolean check,
974
Set<String> visited) {
975
Resource res;
976
String[] jarFiles;
977
int count = 0;
978
LinkedList<String> jarFilesList = null;
979
980
/* If there no jar files in the index that can potential contain
981
* this resource then return immediately.
982
*/
983
if ((jarFilesList = index.get(name)) == null)
984
return null;
985
986
do {
987
int size = jarFilesList.size();
988
jarFiles = jarFilesList.toArray(new String[size]);
989
/* loop through the mapped jar file list */
990
while (count < size) {
991
String jarName = jarFiles[count++];
992
JarLoader newLoader;
993
final URL url;
994
995
try{
996
url = new URL(csu, jarName);
997
String urlNoFragString = URLUtil.urlNoFragString(url);
998
if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
999
/* no loader has been set up for this jar file
1000
* before
1001
*/
1002
newLoader = AccessController.doPrivileged(
1003
new PrivilegedExceptionAction<>() {
1004
public JarLoader run() throws IOException {
1005
return new JarLoader(url, handler,
1006
lmap, acc);
1007
}
1008
}, acc);
1009
1010
/* this newly opened jar file has its own index,
1011
* merge it into the parent's index, taking into
1012
* account the relative path.
1013
*/
1014
JarIndex newIndex = newLoader.getIndex();
1015
if (newIndex != null) {
1016
int pos = jarName.lastIndexOf('/');
1017
newIndex.merge(this.index, (pos == -1 ?
1018
null : jarName.substring(0, pos + 1)));
1019
}
1020
1021
/* put it in the global hashtable */
1022
lmap.put(urlNoFragString, newLoader);
1023
}
1024
} catch (PrivilegedActionException pae) {
1025
continue;
1026
} catch (MalformedURLException e) {
1027
continue;
1028
}
1029
1030
/* Note that the addition of the url to the list of visited
1031
* jars incorporates a check for presence in the hashmap
1032
*/
1033
boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
1034
if (!visitedURL) {
1035
try {
1036
newLoader.ensureOpen();
1037
} catch (IOException e) {
1038
throw new InternalError(e);
1039
}
1040
final JarEntry entry = newLoader.jar.getJarEntry(name);
1041
if (entry != null) {
1042
return newLoader.checkResource(name, check, entry);
1043
}
1044
1045
/* Verify that at least one other resource with the
1046
* same package name as the lookedup resource is
1047
* present in the new jar
1048
*/
1049
if (!newLoader.validIndex(name)) {
1050
/* the mapping is wrong */
1051
throw new InvalidJarIndexError("Invalid index");
1052
}
1053
}
1054
1055
/* If newLoader is the current loader or if it is a
1056
* loader that has already been searched or if the new
1057
* loader does not have an index then skip it
1058
* and move on to the next loader.
1059
*/
1060
if (visitedURL || newLoader == this ||
1061
newLoader.getIndex() == null) {
1062
continue;
1063
}
1064
1065
/* Process the index of the new loader
1066
*/
1067
if ((res = newLoader.getResource(name, check, visited))
1068
!= null) {
1069
return res;
1070
}
1071
}
1072
// Get the list of jar files again as the list could have grown
1073
// due to merging of index files.
1074
jarFilesList = index.get(name);
1075
1076
// If the count is unchanged, we are done.
1077
} while (count < jarFilesList.size());
1078
return null;
1079
}
1080
1081
1082
/*
1083
* Returns the JAR file local class path, or null if none.
1084
*/
1085
@Override
1086
URL[] getClassPath() throws IOException {
1087
if (index != null) {
1088
return null;
1089
}
1090
1091
ensureOpen();
1092
1093
// Only get manifest when necessary
1094
if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {
1095
Manifest man = jar.getManifest();
1096
if (man != null) {
1097
Attributes attr = man.getMainAttributes();
1098
if (attr != null) {
1099
String value = attr.getValue(Name.CLASS_PATH);
1100
if (value != null) {
1101
return parseClassPath(csu, value);
1102
}
1103
}
1104
}
1105
}
1106
return null;
1107
}
1108
1109
/*
1110
* Parses value of the Class-Path manifest attribute and returns
1111
* an array of URLs relative to the specified base URL.
1112
*/
1113
private static URL[] parseClassPath(URL base, String value)
1114
throws MalformedURLException
1115
{
1116
StringTokenizer st = new StringTokenizer(value);
1117
URL[] urls = new URL[st.countTokens()];
1118
int i = 0;
1119
while (st.hasMoreTokens()) {
1120
String path = st.nextToken();
1121
URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : tryResolve(base, path);
1122
if (url != null) {
1123
urls[i] = url;
1124
i++;
1125
} else {
1126
if (DEBUG_CP_URL_CHECK) {
1127
System.err.println("Class-Path entry: \"" + path
1128
+ "\" ignored in JAR file " + base);
1129
}
1130
}
1131
}
1132
if (i == 0) {
1133
urls = null;
1134
} else if (i != urls.length) {
1135
// Truncate nulls from end of array
1136
urls = Arrays.copyOf(urls, i);
1137
}
1138
return urls;
1139
}
1140
1141
static URL tryResolve(URL base, String input) throws MalformedURLException {
1142
if ("file".equalsIgnoreCase(base.getProtocol())) {
1143
return tryResolveFile(base, input);
1144
} else {
1145
return tryResolveNonFile(base, input);
1146
}
1147
}
1148
1149
/**
1150
* Attempt to return a file URL by resolving input against a base file
1151
* URL.
1152
* @return the resolved URL or null if the input is an absolute URL with
1153
* a scheme other than file (ignoring case)
1154
* @throws MalformedURLException
1155
*/
1156
static URL tryResolveFile(URL base, String input) throws MalformedURLException {
1157
URL retVal = new URL(base, input);
1158
if (input.indexOf(':') >= 0 &&
1159
!"file".equalsIgnoreCase(retVal.getProtocol())) {
1160
// 'input' contains a ':', which might be a scheme, or might be
1161
// a Windows drive letter. If the protocol for the resolved URL
1162
// isn't "file:", it should be ignored.
1163
return null;
1164
}
1165
return retVal;
1166
}
1167
1168
/**
1169
* Attempt to return a URL by resolving input against a base URL. Returns
1170
* null if the resolved URL is not contained by the base URL.
1171
*
1172
* @return the resolved URL or null
1173
* @throws MalformedURLException
1174
*/
1175
static URL tryResolveNonFile(URL base, String input) throws MalformedURLException {
1176
String child = input.replace(File.separatorChar, '/');
1177
if (isRelative(child)) {
1178
URL url = new URL(base, child);
1179
String bp = base.getPath();
1180
String urlp = url.getPath();
1181
int pos = bp.lastIndexOf('/');
1182
if (pos == -1) {
1183
pos = bp.length() - 1;
1184
}
1185
if (urlp.regionMatches(0, bp, 0, pos + 1)
1186
&& urlp.indexOf("..", pos) == -1) {
1187
return url;
1188
}
1189
}
1190
return null;
1191
}
1192
1193
/**
1194
* Returns true if the given input is a relative URI.
1195
*/
1196
static boolean isRelative(String child) {
1197
try {
1198
return !URI.create(child).isAbsolute();
1199
} catch (IllegalArgumentException e) {
1200
return false;
1201
}
1202
}
1203
}
1204
1205
/*
1206
* Nested class used to represent a loader of classes and resources
1207
* from a file URL that refers to a directory.
1208
*/
1209
private static class FileLoader extends Loader {
1210
/* Canonicalized File */
1211
private File dir;
1212
1213
/*
1214
* Creates a new FileLoader for the specified URL with a file protocol.
1215
*/
1216
private FileLoader(URL url) throws IOException {
1217
super(url);
1218
String path = url.getFile().replace('/', File.separatorChar);
1219
path = ParseUtil.decode(path);
1220
dir = (new File(path)).getCanonicalFile();
1221
}
1222
1223
/*
1224
* Returns the URL for a resource with the specified name
1225
*/
1226
@Override
1227
URL findResource(final String name, boolean check) {
1228
Resource rsc = getResource(name, check);
1229
if (rsc != null) {
1230
return rsc.getURL();
1231
}
1232
return null;
1233
}
1234
1235
@Override
1236
Resource getResource(final String name, boolean check) {
1237
final URL url;
1238
try {
1239
URL normalizedBase = new URL(getBaseURL(), ".");
1240
url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1241
1242
if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1243
// requested resource had ../..'s in path
1244
return null;
1245
}
1246
1247
if (check)
1248
URLClassPath.check(url);
1249
1250
final File file;
1251
if (name.indexOf("..") != -1) {
1252
file = (new File(dir, name.replace('/', File.separatorChar)))
1253
.getCanonicalFile();
1254
if ( !((file.getPath()).startsWith(dir.getPath())) ) {
1255
/* outside of base dir */
1256
return null;
1257
}
1258
} else {
1259
file = new File(dir, name.replace('/', File.separatorChar));
1260
}
1261
1262
if (file.exists()) {
1263
return new Resource() {
1264
public String getName() { return name; };
1265
public URL getURL() { return url; };
1266
public URL getCodeSourceURL() { return getBaseURL(); };
1267
public InputStream getInputStream() throws IOException
1268
{ return new FileInputStream(file); };
1269
public int getContentLength() throws IOException
1270
{ return (int)file.length(); };
1271
};
1272
}
1273
} catch (Exception e) {
1274
return null;
1275
}
1276
return null;
1277
}
1278
}
1279
}
1280
1281