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