|
29 | 29 | import java.io.InputStream; |
30 | 30 | import java.net.MalformedURLException; |
31 | 31 | import java.net.URL; |
| 32 | +import java.nio.charset.StandardCharsets; |
32 | 33 | import java.util.ArrayList; |
33 | 34 | import java.util.Collections; |
34 | 35 | import java.util.Enumeration; |
|
42 | 43 | import org.graalvm.nativeimage.hosted.Feature; |
43 | 44 |
|
44 | 45 | import com.oracle.svm.core.annotate.AutomaticFeature; |
| 46 | +import com.oracle.svm.core.jdk.resources.NativeImageResourcePath; |
45 | 47 | import com.oracle.svm.core.jdk.resources.ResourceStorageEntry; |
46 | 48 | import com.oracle.svm.core.util.ImageHeapMap; |
47 | 49 | import com.oracle.svm.core.util.VMError; |
@@ -83,57 +85,108 @@ public static byte[] inputStreamToByteArray(InputStream is) { |
83 | 85 | } |
84 | 86 | } |
85 | 87 |
|
86 | | - private static String getResourceWithoutTrailingSlash(String name) { |
87 | | - return name.endsWith("/") ? name.substring(0, name.length() - 1) : name; |
88 | | - } |
89 | | - |
90 | | - private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data) { |
| 88 | + private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data, boolean fromJar) { |
91 | 89 | Resources support = singleton(); |
92 | | - Pair<String, String> key = Pair.create(moduleName, getResourceWithoutTrailingSlash(resourceName)); |
| 90 | + Pair<String, String> key = Pair.create(moduleName, resourceName); |
93 | 91 | ResourceStorageEntry entry = support.resources.get(key); |
94 | 92 | if (entry == null) { |
95 | | - entry = new ResourceStorageEntry(isDirectory); |
| 93 | + entry = new ResourceStorageEntry(isDirectory, fromJar); |
96 | 94 | support.resources.put(key, entry); |
97 | 95 | } |
98 | 96 | entry.getData().add(data); |
99 | 97 | } |
100 | 98 |
|
101 | 99 | @Platforms(Platform.HOSTED_ONLY.class) |
102 | 100 | public static void registerResource(String resourceName, InputStream is) { |
103 | | - registerResource(null, resourceName, is); |
| 101 | + registerResource(null, resourceName, is, true); |
| 102 | + } |
| 103 | + |
| 104 | + @Platforms(Platform.HOSTED_ONLY.class) |
| 105 | + public static void registerResource(String resourceName, InputStream is, boolean fromJar) { |
| 106 | + registerResource(null, resourceName, is, fromJar); |
104 | 107 | } |
105 | 108 |
|
106 | 109 | @Platforms(Platform.HOSTED_ONLY.class) |
107 | 110 | public static void registerResource(String moduleName, String resourceName, InputStream is) { |
108 | | - addEntry(moduleName, resourceName, false, inputStreamToByteArray(is)); |
| 111 | + registerResource(moduleName, resourceName, is, true); |
| 112 | + } |
| 113 | + |
| 114 | + @Platforms(Platform.HOSTED_ONLY.class) |
| 115 | + public static void registerResource(String moduleName, String resourceName, InputStream is, boolean fromJar) { |
| 116 | + addEntry(moduleName, resourceName, false, inputStreamToByteArray(is), fromJar); |
109 | 117 | } |
110 | 118 |
|
111 | 119 | @Platforms(Platform.HOSTED_ONLY.class) |
112 | 120 | public static void registerDirectoryResource(String resourceDirName, String content) { |
113 | | - registerDirectoryResource(null, resourceDirName, content); |
| 121 | + registerDirectoryResource(null, resourceDirName, content, true); |
| 122 | + } |
| 123 | + |
| 124 | + @Platforms(Platform.HOSTED_ONLY.class) |
| 125 | + public static void registerDirectoryResource(String resourceDirName, String content, boolean fromJar) { |
| 126 | + registerDirectoryResource(null, resourceDirName, content, fromJar); |
114 | 127 | } |
115 | 128 |
|
116 | 129 | @Platforms(Platform.HOSTED_ONLY.class) |
117 | 130 | public static void registerDirectoryResource(String moduleName, String resourceDirName, String content) { |
| 131 | + registerDirectoryResource(moduleName, resourceDirName, content, true); |
| 132 | + } |
| 133 | + |
| 134 | + @Platforms(Platform.HOSTED_ONLY.class) |
| 135 | + public static void registerDirectoryResource(String moduleName, String resourceDirName, String content, boolean fromJar) { |
118 | 136 | /* |
119 | 137 | * A directory content represents the names of all files and subdirectories located in the |
120 | 138 | * specified directory, separated with new line delimiter and joined into one string which |
121 | 139 | * is later converted into a byte array and placed into the resources map. |
122 | 140 | */ |
123 | | - addEntry(moduleName, resourceDirName, true, content.getBytes()); |
| 141 | + addEntry(moduleName, resourceDirName, true, content.getBytes(), fromJar); |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * Avoid pulling native file system by using {@link NativeImageResourcePath} implementation to |
| 146 | + * convert <code>resourceName</code> to canonical variant. |
| 147 | + */ |
| 148 | + public static String toCanonicalForm(String resourceName) { |
| 149 | + NativeImageResourcePath path = new NativeImageResourcePath(null, removeTrailingSlash(resourceName).getBytes(StandardCharsets.UTF_8), true); |
| 150 | + return new String(NativeImageResourcePath.getResolved(path)); |
| 151 | + } |
| 152 | + |
| 153 | + private static boolean hasTrailingSlash(String resourceName) { |
| 154 | + return resourceName.endsWith("/"); |
| 155 | + } |
| 156 | + |
| 157 | + private static String removeTrailingSlash(String resourceName) { |
| 158 | + return hasTrailingSlash(resourceName) ? resourceName.substring(0, resourceName.length() - 1) : resourceName; |
| 159 | + } |
| 160 | + |
| 161 | + private static boolean wasAlreadyInCanonicalForm(String resourceName, String canonicalResourceName) { |
| 162 | + return resourceName.equals(canonicalResourceName) || removeTrailingSlash(resourceName).equals(canonicalResourceName); |
124 | 163 | } |
125 | 164 |
|
126 | 165 | public static ResourceStorageEntry get(String name) { |
127 | 166 | return get(null, name); |
128 | 167 | } |
129 | 168 |
|
130 | 169 | public static ResourceStorageEntry get(String moduleName, String resourceName) { |
131 | | - ResourceStorageEntry resourceStorageEntry = singleton().resources.get(Pair.create(moduleName, getResourceWithoutTrailingSlash(resourceName))); |
132 | | - if (resourceStorageEntry != null && (resourceStorageEntry.isDirectory() || !resourceName.endsWith("/"))) { |
133 | | - return resourceStorageEntry; |
134 | | - } else { |
| 170 | + String canonicalResourceName = toCanonicalForm(resourceName); |
| 171 | + ResourceStorageEntry entry = singleton().resources.get(Pair.create(moduleName, canonicalResourceName)); |
| 172 | + if (entry == null) { |
| 173 | + return null; |
| 174 | + } |
| 175 | + if (entry.isFromJar() && !wasAlreadyInCanonicalForm(resourceName, canonicalResourceName)) { |
| 176 | + /* |
| 177 | + * The resource originally came from a jar file, thus behave like ZipFileSystem behaves |
| 178 | + * for non-canonical paths. |
| 179 | + */ |
135 | 180 | return null; |
136 | 181 | } |
| 182 | + if (!entry.isDirectory() && hasTrailingSlash(resourceName)) { |
| 183 | + /* |
| 184 | + * It this an actual resource file (not a directory) we do not tolerate a trailing |
| 185 | + * slash. |
| 186 | + */ |
| 187 | + return null; |
| 188 | + } |
| 189 | + return entry; |
137 | 190 | } |
138 | 191 |
|
139 | 192 | private static URL createURL(String moduleName, String resourceName, int index) { |
@@ -186,30 +239,31 @@ public static Enumeration<URL> createURLs(String moduleName, String resourceName |
186 | 239 | } |
187 | 240 |
|
188 | 241 | List<URL> resourcesURLs = new ArrayList<>(); |
189 | | - |
| 242 | + String canonicalResourceName = toCanonicalForm(resourceName); |
| 243 | + boolean shouldAppendTrailingSlash = hasTrailingSlash(resourceName); |
190 | 244 | /* If moduleName was unspecified we have to consider all modules in the image */ |
191 | 245 | if (moduleName == null) { |
192 | 246 | for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) { |
193 | 247 | ResourceStorageEntry entry = Resources.get(module.getName(), resourceName); |
194 | | - addURLEntries(resourcesURLs, entry, module.getName(), resourceName); |
| 248 | + addURLEntries(resourcesURLs, entry, module.getName(), shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName); |
195 | 249 | } |
196 | 250 | } |
197 | 251 | ResourceStorageEntry explicitEntry = Resources.get(moduleName, resourceName); |
198 | | - addURLEntries(resourcesURLs, explicitEntry, moduleName, resourceName); |
| 252 | + addURLEntries(resourcesURLs, explicitEntry, moduleName, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName); |
199 | 253 |
|
200 | 254 | if (resourcesURLs.isEmpty()) { |
201 | 255 | return Collections.emptyEnumeration(); |
202 | 256 | } |
203 | 257 | return Collections.enumeration(resourcesURLs); |
204 | 258 | } |
205 | 259 |
|
206 | | - private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String resourceName) { |
| 260 | + private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String canonicalResourceName) { |
207 | 261 | if (entry == null) { |
208 | 262 | return; |
209 | 263 | } |
210 | 264 | int numberOfResources = entry.getData().size(); |
211 | 265 | for (int index = 0; index < numberOfResources; index++) { |
212 | | - resourcesURLs.add(createURL(moduleName, resourceName, index)); |
| 266 | + resourcesURLs.add(createURL(moduleName, canonicalResourceName, index)); |
213 | 267 | } |
214 | 268 | } |
215 | 269 | } |
|
0 commit comments