1 /*
2 * Copyright (c) 2014, 2025, 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 sun.net.www.protocol.jrt;
27
28 import java.io.ByteArrayInputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.UncheckedIOException;
32 import java.net.MalformedURLException;
33 import java.net.URL;
34
35 import jdk.internal.jimage.ImageReader;
36 import jdk.internal.jimage.ImageReader.Node;
37 import jdk.internal.jimage.SystemImageReader;
38
39 import sun.net.www.ParseUtil;
40 import sun.net.www.URLConnection;
41
42 /**
43 * URLConnection implementation that can be used to connect to resources
44 * contained in the runtime image. See section "New URI scheme for naming stored
45 * modules, classes, and resources" in <a href="https://openjdk.org/jeps/220">
46 * JEP 220</a>.
47 */
48 public class JavaRuntimeURLConnection extends URLConnection {
49
50 // ImageReader to access resources in jimage.
51 private static final ImageReader READER = SystemImageReader.get();
52
53 // The module and resource name in the URL (i.e. "jrt:/[$MODULE[/$PATH]]").
54 //
55 // The module name is not percent-decoded, and can be empty.
56 private final String module;
57 // The resource name permits UTF-8 percent encoding of non-ASCII characters.
58 private final String path;
59
60 // The resource node (non-null when connected).
61 private Node resourceNode;
62
63 JavaRuntimeURLConnection(URL url) throws IOException {
64 super(url);
65 String urlPath = url.getPath();
66 if (urlPath.isEmpty() || urlPath.charAt(0) != '/') {
67 throw new MalformedURLException(url + " missing path or /");
68 }
69 int pathSep = urlPath.indexOf('/', 1);
70 if (pathSep == -1) {
71 // No trailing resource path. This can never "connect" or return a
72 // resource (see JEP 220 for details).
73 this.module = urlPath.substring(1);
74 this.path = null;
75 } else {
76 this.module = urlPath.substring(1, pathSep);
77 this.path = percentDecode(urlPath.substring(pathSep + 1));
78 }
79 }
80
81 /**
82 * Finds and caches the resource node associated with this URL and marks the
83 * connection as "connected".
84 */
85 private synchronized Node connectResourceNode() throws IOException {
86 if (resourceNode == null) {
87 if (module.isEmpty() || path == null) {
88 throw new IOException("cannot connect to jrt:/" + module);
89 }
90 Node node = READER.findResourceNode(module, path);
91 if (node == null) {
92 throw new IOException(module + "/" + path + " not found");
93 }
94 this.resourceNode = node;
95 super.connected = true;
96 }
97 return resourceNode;
98 }
99
100 @Override
101 public void connect() throws IOException {
102 connectResourceNode();
103 }
104
105 @Override
106 public InputStream getInputStream() throws IOException {
107 return new ByteArrayInputStream(READER.getResource(connectResourceNode()));
108 }
109
110 @Override
111 public long getContentLengthLong() {
112 // Note: UncheckedIOException is thrown by the Node subclass in
113 // ExplodedImage (this not obvious, so worth calling out).
114 try {
115 return connectResourceNode().size();
116 } catch (IOException | UncheckedIOException ioe) {
117 return -1L;
118 }
119 }
120
121 @Override
122 public int getContentLength() {
123 long len = getContentLengthLong();
124 return len > Integer.MAX_VALUE ? -1 : (int)len;
125 }
126
127 // Perform percent decoding of the resource name/path from the URL.
128 private static String percentDecode(String path) throws MalformedURLException {
129 if (path.indexOf('%') == -1) {
130 // Nothing to decode (overwhelmingly common case).
131 return path;
132 }
133 // Any additional special case decoding logic should go here.
134 try {
135 return ParseUtil.decode(path);
136 } catch (IllegalArgumentException e) {
137 throw new MalformedURLException(e.getMessage());
138 }
139 }
140 }