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