1 /* 2 * Copyright (c) 2011, 2016, 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 javafx.css.converter; 27 28 import javafx.application.Application; 29 import javafx.css.ParsedValue; 30 import javafx.css.StyleConverter; 31 import javafx.scene.text.Font; 32 import sun.util.logging.PlatformLogger; 33 34 import java.net.MalformedURLException; 35 import java.net.URI; 36 import java.net.URISyntaxException; 37 import java.net.URL; 38 import java.security.AccessController; 39 import java.security.CodeSource; 40 import java.security.PrivilegedActionException; 41 import java.security.PrivilegedExceptionAction; 42 import java.security.ProtectionDomain; 43 44 /** 45 * Convert url("<path>") a URL string resolved relative to the location of the stylesheet. 46 * 47 * @since 9 48 */ 49 public final class URLConverter extends StyleConverter<ParsedValue[], String> { 50 51 // lazy, thread-safe instatiation 52 private static class Holder { 53 static final URLConverter INSTANCE = new URLConverter(); 54 static final SequenceConverter SEQUENCE_INSTANCE = new SequenceConverter(); 55 } 56 57 public static StyleConverter<ParsedValue[], String> getInstance() { 58 return Holder.INSTANCE; 59 } 60 61 private URLConverter() { 62 super(); 63 } 64 65 @Override 66 public String convert(ParsedValue<ParsedValue[], String> value, Font font) { 67 68 String url = null; 69 70 ParsedValue[] values = value.getValue(); 71 72 String resource = values.length > 0 ? StringConverter.getInstance().convert(values[0], font) : null; 73 74 if (resource != null && resource.trim().isEmpty() == false) { 75 76 if (resource.startsWith("url(")) { 77 resource = com.sun.javafx.util.Utils.stripQuotes(resource.substring(4, resource.length() - 1)); 78 } else { 79 resource = com.sun.javafx.util.Utils.stripQuotes(resource); 80 } 81 82 String stylesheetURL = values.length > 1 && values[1] != null ? (String)values[1].getValue() : null; 83 URL resolvedURL = resolve(stylesheetURL, resource); 84 85 if (resolvedURL != null) url = resolvedURL.toExternalForm(); 86 } 87 88 return url; 89 } 90 91 // package for testing 92 URL resolve(String stylesheetUrl, String resource) { 93 94 95 final String resourcePath = (resource != null) ? resource.trim() : null; 96 if (resourcePath == null || resourcePath.isEmpty()) return null; 97 98 try { 99 100 // Note: the same code (pretty much) also appears in StyleManager 101 102 // if stylesheetUri is null, then we're dealing with an in-line style. 103 // If there is no scheme part, then the url is interpreted as being relative to the application's class-loader. 104 URI resourceUri = new URI(resourcePath); 105 106 if (resourceUri.isAbsolute()) { 107 return resourceUri.toURL(); 108 } 109 110 URL rtJarUrl = resolveRuntimeImport(resourceUri); 111 if (rtJarUrl != null) { 112 return rtJarUrl; 113 } 114 115 final String path = resourceUri.getPath(); 116 if (path.startsWith("/")) { 117 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 118 // FIXME: JIGSAW -- use Class.getResource if resource is in a module 119 return contextClassLoader.getResource(path.substring(1)); 120 } 121 122 final String stylesheetPath = (stylesheetUrl != null) ? stylesheetUrl.trim() : null; 123 124 if (stylesheetPath != null && stylesheetPath.isEmpty() == false) { 125 126 URI stylesheetUri = new URI(stylesheetPath); 127 128 if (stylesheetUri.isOpaque() == false) { 129 130 URI resolved = stylesheetUri.resolve(resourceUri); 131 return resolved.toURL(); 132 133 } else { 134 135 // stylesheet URI is something like jar:file: 136 URL url = stylesheetUri.toURL(); 137 return new URL(url, resourceUri.getPath()); 138 } 139 } 140 141 142 // URL doesn't have scheme or stylesheetUrl is null 143 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 144 // FIXME: JIGSAW -- use Class.getResource if resource is in a module 145 return contextClassLoader.getResource(path); 146 147 148 } catch (final MalformedURLException|URISyntaxException e) { 149 PlatformLogger cssLogger = com.sun.javafx.util.Logging.getCSSLogger(); 150 if (cssLogger.isLoggable(PlatformLogger.Level.WARNING)) { 151 cssLogger.warning(e.getLocalizedMessage()); 152 } 153 154 return null; 155 } 156 157 } 158 159 160 // 161 // Resolve a path from an @import that implies jfxrt.jar, 162 // e.g., @import "com/sun/javafx/scene/control/skin/modena/modena.css". 163 // 164 // See also StyleSheet#loadStylesheet(String) 165 // 166 private URL resolveRuntimeImport(final URI resourceUri) { 167 168 // FIXME: JIGSAW -- this method needs to be rewritten for Jigsaw. 169 // There is no jfxrt.jar any more, and resource encapsulation will 170 // prevent it from being resolved anyway. 171 172 final String path = resourceUri.getPath(); 173 final String resourcePath = path.startsWith("/") ? path.substring(1) : path; 174 175 if ((resourcePath.startsWith("com/sun/javafx/scene/control/skin/modena/") || 176 resourcePath.startsWith("com/sun/javafx/scene/control/skin/caspian/")) && 177 (resourcePath.endsWith(".css") || resourcePath.endsWith(".bss"))) { 178 179 System.err.println("WARNING: resolveRuntimeImport cannot resolve: " + resourcePath); 180 181 final SecurityManager sm = System.getSecurityManager(); 182 if (sm == null) { 183 // If the SecurityManager is not null, then just look up the resource on the class-path. 184 // If there is a SecurityManager, the URLClassPath getResource call will return null, 185 // so fall through and create a URL from the code-source URI 186 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 187 // FIXME: JIGSAW -- use Class.getResource if resource is in a module 188 final URL resolved = contextClassLoader.getResource(resourcePath); 189 return resolved; 190 } 191 192 // check whether the path is file from our runtime jar 193 try { 194 final URL rtJarURL = AccessController.doPrivileged((PrivilegedExceptionAction<URL>) () -> { 195 // getProtectionDomain either throws a SecurityException or returns a non-null value 196 final ProtectionDomain protectionDomain = Application.class.getProtectionDomain(); 197 // If we're running with a SecurityManager, then the ProtectionDomain will have a CodeSource 198 final CodeSource codeSource = protectionDomain.getCodeSource(); 199 // The CodeSource location will be our runtime jar 200 return codeSource.getLocation(); 201 }); 202 203 final URI rtJarURI = rtJarURL.toURI(); 204 205 String scheme = rtJarURI.getScheme(); 206 String rtJarPath = rtJarURI.getPath(); 207 208 // 209 // Just because we're running with a SecurityManager doesn't mean the jfxrt jar path is 210 // a jar: URL. But the code in StyleManager wants it to be. So if we have 211 // file:/blah/lib/jfxrt.jar make it jar:file:/blah/lib/jfxrt.jar!/ 212 // 213 // If the path doesn't end with .jar, then we are just dealing with a normal file: path 214 // 215 if ("file".equals(scheme) && rtJarPath.endsWith(".jar")) { 216 if ("file".equals(scheme)) { 217 scheme = "jar:file"; 218 rtJarPath = rtJarPath.concat("!/"); 219 } 220 } 221 rtJarPath = rtJarPath.concat(resourcePath); 222 223 final String rtJarUserInfo = rtJarURI.getUserInfo(); 224 final String rtJarHost = rtJarURI.getHost(); 225 final int rtJarPort = rtJarURI.getPort(); 226 227 // 228 // Put together a new URI from the pieces of rtJarURI. We cannot use resolve here since 229 // the scheme and path may have been munged. 230 // 231 URI resolved = new URI(scheme, rtJarUserInfo, rtJarHost, rtJarPort, rtJarPath, null, null); 232 return resolved.toURL(); 233 234 } catch (URISyntaxException | MalformedURLException | PrivilegedActionException ignored) { 235 // Allow this method to return null so the caller will try to further resolve the path. 236 // If nothing else, an error message will result when the converted URL is consumed. 237 } 238 } 239 return null; 240 } 241 242 @Override 243 public String toString() { 244 return "URLType"; 245 } 246 247 public static final class SequenceConverter extends StyleConverter<ParsedValue<ParsedValue[], String>[], String[]> { 248 249 public static SequenceConverter getInstance() { 250 return Holder.SEQUENCE_INSTANCE; 251 } 252 253 private SequenceConverter() { 254 super(); 255 } 256 257 @Override 258 public String[] convert(ParsedValue<ParsedValue<ParsedValue[], String>[], String[]> value, Font font) { 259 ParsedValue<ParsedValue[], String>[] layers = value.getValue(); 260 String[] urls = new String[layers.length]; 261 for (int layer = 0; layer < layers.length; layer++) { 262 urls[layer] = URLConverter.getInstance().convert(layers[layer], font); 263 } 264 return urls; 265 } 266 267 @Override 268 public String toString() { 269 return "URLSeqType"; 270 } 271 } 272 273 }