1 /* 2 * Copyright (c) 2011, 2015, 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 return contextClassLoader.getResource(path.substring(1)); 119 } 120 121 final String stylesheetPath = (stylesheetUrl != null) ? stylesheetUrl.trim() : null; 122 123 if (stylesheetPath != null && stylesheetPath.isEmpty() == false) { 124 125 URI stylesheetUri = new URI(stylesheetPath); 126 127 if (stylesheetUri.isOpaque() == false) { 128 129 URI resolved = stylesheetUri.resolve(resourceUri); 130 return resolved.toURL(); 131 132 } else { 133 134 // stylesheet URI is something like jar:file: 135 URL url = stylesheetUri.toURL(); 136 return new URL(url, resourceUri.getPath()); 137 } 138 } 139 140 141 // URL doesn't have scheme or stylesheetUrl is null 142 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 143 return contextClassLoader.getResource(path); 144 145 146 } catch (final MalformedURLException|URISyntaxException e) { 147 PlatformLogger cssLogger = com.sun.javafx.util.Logging.getCSSLogger(); 148 if (cssLogger.isLoggable(PlatformLogger.Level.WARNING)) { 149 cssLogger.warning(e.getLocalizedMessage()); 150 } 151 152 return null; 153 } 154 155 } 156 157 158 // 159 // Resolve a path from an @import that implies jfxrt.jar, 160 // e.g., @import "com/sun/javafx/scene/control/skin/modena/modena.css". 161 // 162 // See also StyleSheet#loadStylesheet(String) 163 // 164 private URL resolveRuntimeImport(final URI resourceUri) { 165 166 167 final String path = resourceUri.getPath(); 168 final String resourcePath = path.startsWith("/") ? path.substring(1) : path; 169 170 if ((resourcePath.startsWith("com/sun/javafx/scene/control/skin/modena/") || 171 resourcePath.startsWith("com/sun/javafx/scene/control/skin/caspian/")) && 172 (resourcePath.endsWith(".css") || resourcePath.endsWith(".bss"))) { 173 174 final SecurityManager sm = System.getSecurityManager(); 175 if (sm == null) { 176 // If the SecurityManager is not null, then just look up the resource on the class-path. 177 // If there is a SecurityManager, the URLClassPath getResource call will return null, 178 // so fall through and create a URL from the code-source URI 179 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 180 final URL resolved = contextClassLoader.getResource(resourcePath); 181 return resolved; 182 } 183 184 // check whether the path is file from our runtime jar 185 try { 186 final URL rtJarURL = AccessController.doPrivileged((PrivilegedExceptionAction<URL>) () -> { 187 // getProtectionDomain either throws a SecurityException or returns a non-null value 188 final ProtectionDomain protectionDomain = Application.class.getProtectionDomain(); 189 // If we're running with a SecurityManager, then the ProtectionDomain will have a CodeSource 190 final CodeSource codeSource = protectionDomain.getCodeSource(); 191 // The CodeSource location will be our runtime jar 192 return codeSource.getLocation(); 193 }); 194 195 final URI rtJarURI = rtJarURL.toURI(); 196 197 String scheme = rtJarURI.getScheme(); 198 String rtJarPath = rtJarURI.getPath(); 199 200 // 201 // Just because we're running with a SecurityManager doesn't mean the jfxrt jar path is 202 // a jar: URL. But the code in StyleManager wants it to be. So if we have 203 // file:/blah/lib/jfxrt.jar make it jar:file:/blah/lib/jfxrt.jar!/ 204 // 205 // If the path doesn't end with .jar, then we are just dealing with a normal file: path 206 // 207 if ("file".equals(scheme) && rtJarPath.endsWith(".jar")) { 208 if ("file".equals(scheme)) { 209 scheme = "jar:file"; 210 rtJarPath = rtJarPath.concat("!/"); 211 } 212 } 213 rtJarPath = rtJarPath.concat(resourcePath); 214 215 final String rtJarUserInfo = rtJarURI.getUserInfo(); 216 final String rtJarHost = rtJarURI.getHost(); 217 final int rtJarPort = rtJarURI.getPort(); 218 219 // 220 // Put together a new URI from the pieces of rtJarURI. We cannot use resolve here since 221 // the scheme and path may have been munged. 222 // 223 URI resolved = new URI(scheme, rtJarUserInfo, rtJarHost, rtJarPort, rtJarPath, null, null); 224 return resolved.toURL(); 225 226 } catch (URISyntaxException | MalformedURLException | PrivilegedActionException ignored) { 227 // Allow this method to return null so the caller will try to further resolve the path. 228 // If nothing else, an error message will result when the converted URL is consumed. 229 } 230 } 231 return null; 232 } 233 234 @Override 235 public String toString() { 236 return "URLType"; 237 } 238 239 public static final class SequenceConverter extends StyleConverter<ParsedValue<ParsedValue[], String>[], String[]> { 240 241 public static SequenceConverter getInstance() { 242 return Holder.SEQUENCE_INSTANCE; 243 } 244 245 private SequenceConverter() { 246 super(); 247 } 248 249 @Override 250 public String[] convert(ParsedValue<ParsedValue<ParsedValue[], String>[], String[]> value, Font font) { 251 ParsedValue<ParsedValue[], String>[] layers = value.getValue(); 252 String[] urls = new String[layers.length]; 253 for (int layer = 0; layer < layers.length; layer++) { 254 urls[layer] = URLConverter.getInstance().convert(layers[layer], font); 255 } 256 return urls; 257 } 258 259 @Override 260 public String toString() { 261 return "URLSeqType"; 262 } 263 } 264 265 }