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