1 // Copyright (C) 2010 Red Hat, Inc. 2 // 3 // This library is free software; you can redistribute it and/or 4 // modify it under the terms of the GNU Lesser General Public 5 // License as published by the Free Software Foundation; either 6 // version 2.1 of the License, or (at your option) any later version. 7 // 8 // This library is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 // Lesser General Public License for more details. 12 // 13 // You should have received a copy of the GNU Lesser General Public 14 // License along with this library; if not, write to the Free Software 15 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 16 17 package net.sourceforge.jnlp.config; 18 19 import static net.sourceforge.jnlp.runtime.Translator.R; 20 21 import java.io.BufferedOutputStream; 22 import java.io.BufferedReader; 23 import java.io.File; 24 import java.io.FileOutputStream; 25 import java.io.FileReader; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.io.PrintStream; 29 import java.io.Reader; 30 import java.net.MalformedURLException; 31 import java.net.URL; 32 import java.nio.channels.FileLock; 33 import java.text.SimpleDateFormat; 34 import java.util.Date; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Properties; 38 import java.util.Set; 39 40 import javax.naming.ConfigurationException; 41 import javax.swing.JOptionPane; 42 43 import net.sourceforge.jnlp.runtime.JNLPRuntime; 44 import net.sourceforge.jnlp.util.FileUtils; 45 import net.sourceforge.jnlp.util.logging.OutputController; 46 47 /** 48 * Manages the various properties and configuration related to deployment. 49 * 50 * See: 51 * http://download.oracle.com/javase/1.5.0/docs/guide/deployment/deployment-guide/properties.html 52 */ 53 public final class DeploymentConfiguration { 54 55 public static final String DEPLOYMENT_CONFIG_FILE = "deployment.config"; 56 public static final String DEPLOYMENT_PROPERTIES = "deployment.properties"; 57 public static final String APPLET_TRUST_SETTINGS = ".appletTrustSettings"; 58 59 public static final String DEPLOYMENT_COMMENT = "Netx deployment configuration"; 60 public String userComments; 61 public String systemComments; 62 63 public static final int JNLP_ASSOCIATION_NEVER = 0; 64 public static final int JNLP_ASSOCIATION_NEW_ONLY = 1; 65 public static final int JNLP_ASSOCIATION_ASK_USER = 2; 66 public static final int JNLP_ASSOCIATION_REPLACE_ASK = 3; 67 68 /** 69 * when set to as value of KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode", 70 * then console is not visible by default, but may be shown 71 */ 72 public static final String CONSOLE_HIDE = "HIDE"; 73 /** 74 * when set to as value of KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode", 75 * then console show for both javaws and plugin 76 */ 77 public static final String CONSOLE_SHOW = "SHOW"; 78 /** 79 * when set to as value of KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode", 80 * then console is not visible by default, nop data are passed to it (save memory and cpu) but can not be shown 81 */ 82 public static final String CONSOLE_DISABLE = "DISABLE"; 83 /** 84 * when set to as value of KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode", 85 * then console show for plugin 86 */ 87 public static final String CONSOLE_SHOW_PLUGIN = "SHOW_PLUGIN_ONLY"; 88 /** 89 * when set to as value of KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode", 90 * then console show for javaws 91 */ 92 public static final String CONSOLE_SHOW_JAVAWS = "SHOW_JAVAWS_ONLY"; 93 94 public static final String KEY_USER_CACHE_DIR = "deployment.user.cachedir"; 95 public static final String KEY_USER_PERSISTENCE_CACHE_DIR = "deployment.user.pcachedir"; 96 public static final String KEY_SYSTEM_CACHE_DIR = "deployment.system.cachedir"; 97 98 public static final String KEY_CACHE_MAX_SIZE = "deployment.cache.max.size"; 99 100 public static final String KEY_CACHE_ENABLED = "deployment.javapi.cache.enabled"; 101 public static final String KEY_CACHE_COMPRESSION_ENABLED = "deployment.cache.jarcompression"; 102 103 public static final String KEY_USER_LOG_DIR = "deployment.user.logdir"; 104 public static final String KEY_USER_TMP_DIR = "deployment.user.tmp"; 105 /** the directory containing locks for single instance applications */ 106 public static final String KEY_USER_LOCKS_DIR = "deployment.user.locksdir"; 107 /** 108 * The netx_running file is used to indicate if any instances of netx are 109 * running (this file may exist even if no instances are running). All netx 110 * instances acquire a shared lock on this file. If this file can be locked 111 * (using a {@link FileLock}) in exclusive mode, then other netx instances 112 * are not running 113 */ 114 public static final String KEY_USER_NETX_RUNNING_FILE = "deployment.user.runningfile"; 115 116 public static final String KEY_USER_SECURITY_POLICY = "deployment.user.security.policy"; 117 public static final String KEY_USER_TRUSTED_CA_CERTS = "deployment.user.security.trusted.cacerts"; 118 public static final String KEY_USER_TRUSTED_JSSE_CA_CERTS = "deployment.user.security.trusted.jssecacerts"; 119 public static final String KEY_USER_TRUSTED_CERTS = "deployment.user.security.trusted.certs"; 120 public static final String KEY_USER_TRUSTED_JSSE_CERTS = "deployment.user.security.trusted.jssecerts"; 121 public static final String KEY_USER_TRUSTED_CLIENT_CERTS = "deployment.user.security.trusted.clientauthcerts"; 122 123 public static final String KEY_SYSTEM_SECURITY_POLICY = "deployment.system.security.policy"; 124 public static final String KEY_SYSTEM_TRUSTED_CA_CERTS = "deployment.system.security.cacerts"; 125 public static final String KEY_SYSTEM_TRUSTED_JSSE_CA_CERTS = "deployment.system.security.jssecacerts"; 126 public static final String KEY_SYSTEM_TRUSTED_CERTS = "deployment.system.security.trusted.certs"; 127 public static final String KEY_SYSTEM_TRUSTED_JSSE_CERTS = "deployment.system.security.trusted.jssecerts"; 128 public static final String KEY_SYSTEM_TRUSTED_CLIENT_CERTS = "deployment.system.security.trusted.clientautcerts"; 129 130 /* 131 * Security and access control 132 */ 133 134 /** Boolean. Only show security prompts to user if true */ 135 public static final String KEY_SECURITY_PROMPT_USER = "deployment.security.askgrantdialog.show"; 136 137 //enum of AppletSecurityLevel in result 138 public static final String KEY_SECURITY_LEVEL = "deployment.security.level"; 139 140 public static final String KEY_SECURITY_TRUSTED_POLICY = "deployment.security.trusted.policy"; 141 142 /** Boolean. Only give AWTPermission("showWindowWithoutWarningBanner") if true */ 143 public static final String KEY_SECURITY_ALLOW_HIDE_WINDOW_WARNING = "deployment.security.sandbox.awtwarningwindow"; 144 145 /** Boolean. Only prompt user for granting any JNLP permissions if true */ 146 public static final String KEY_SECURITY_PROMPT_USER_FOR_JNLP = "deployment.security.sandbox.jnlp.enhanced"; 147 148 /** Boolean. Only install the custom authenticator if true */ 149 public static final String KEY_SECURITY_INSTALL_AUTHENTICATOR = "deployment.security.authenticator"; 150 151 public static final String KEY_STRICT_JNLP_CLASSLOADER = "deployment.jnlpclassloader.strict"; 152 /* 153 * Networking 154 */ 155 156 /** the proxy type. possible values are {@code JNLPProxySelector.PROXY_TYPE_*} */ 157 public static final String KEY_PROXY_TYPE = "deployment.proxy.type"; 158 159 /** Boolean. If true, the http host/port should be used for https and ftp as well */ 160 public static final String KEY_PROXY_SAME = "deployment.proxy.same"; 161 162 public static final String KEY_PROXY_AUTO_CONFIG_URL = "deployment.proxy.auto.config.url"; 163 public static final String KEY_PROXY_BYPASS_LIST = "deployment.proxy.bypass.list"; 164 public static final String KEY_PROXY_BYPASS_LOCAL = "deployment.proxy.bypass.local"; 165 public static final String KEY_PROXY_HTTP_HOST = "deployment.proxy.http.host"; 166 public static final String KEY_PROXY_HTTP_PORT = "deployment.proxy.http.port"; 167 public static final String KEY_PROXY_HTTPS_HOST = "deployment.proxy.https.host"; 168 public static final String KEY_PROXY_HTTPS_PORT = "deployment.proxy.https.port"; 169 public static final String KEY_PROXY_FTP_HOST = "deployment.proxy.ftp.host"; 170 public static final String KEY_PROXY_FTP_PORT = "deployment.proxy.ftp.port"; 171 public static final String KEY_PROXY_SOCKS4_HOST = "deployment.proxy.socks.host"; 172 public static final String KEY_PROXY_SOCKS4_PORT = "deployment.proxy.socks.port"; 173 public static final String KEY_PROXY_OVERRIDE_HOSTS = "deployment.proxy.override.hosts"; 174 175 /* 176 * Logging 177 */ 178 public static final String KEY_ENABLE_LOGGING = "deployment.log"; //same as verbose or ICEDTEAPLUGIN_DEBUG=true 179 public static final String KEY_ENABLE_LOGGING_HEADERS = "deployment.log.headers"; //will add header OutputContorll.getHeader To all messages 180 public static final String KEY_ENABLE_LOGGING_TOFILE = "deployment.log.file"; 181 public static final String KEY_ENABLE_APPLICATION_LOGGING_TOFILE ="deployment.log.file.clientapp"; //also client app will log to its separate file 182 public static final String KEY_ENABLE_LEGACY_LOGBASEDFILELOG = "deployment.log.file.legacylog"; 183 public static final String KEY_ENABLE_LOGGING_TOSTREAMS = "deployment.log.stdstreams"; 184 public static final String KEY_ENABLE_LOGGING_TOSYSTEMLOG = "deployment.log.system"; 185 186 /* 187 * manifest check 188 */ 189 public static final String KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK = "deployment.manifest.attributes.check"; 190 191 /** 192 * Console initial status. 193 * One of CONSOLE_* values 194 * See declaration above: 195 * CONSOLE_HIDE = "HIDE"; 196 * CONSOLE_SHOW = "SHOW"; 197 * CONSOLE_DISABLE = "DISABLE"; 198 * CONSOLE_SHOW_PLUGIN = "SHOW_PLUGIN_ONLY"; 199 * CONSOLE_SHOW_JAVAWS = "SHOW_JAVAWS_ONLY"; 200 */ 201 public static final String KEY_CONSOLE_STARTUP_MODE = "deployment.console.startup.mode"; 202 203 204 205 /* 206 * Desktop Integration 207 */ 208 209 public static final String KEY_JNLP_ASSOCIATIONS = "deployment.javaws.associations"; 210 public static final String KEY_CREATE_DESKTOP_SHORTCUT = "deployment.javaws.shortcut"; 211 212 public static final String KEY_JRE_INTSTALL_URL = "deployment.javaws.installURL"; 213 public static final String KEY_AUTO_DOWNLOAD_JRE = "deployment.javaws.autodownload"; 214 215 public static final String KEY_BROWSER_PATH = "deployment.browser.path"; 216 public static final String KEY_UPDATE_TIMEOUT = "deployment.javaws.update.timeout"; 217 218 /* 219 * JVM arguments for plugin 220 */ 221 public static final String KEY_PLUGIN_JVM_ARGUMENTS= "deployment.plugin.jvm.arguments"; 222 public static final String KEY_JRE_DIR= "deployment.jre.dir"; 223 224 225 public static final String TRANSFER_TITLE = "Legacy configuration and cache found. Those will be now transported to new locations"; 226 227 private ConfigurationException loadingException = null; 228 setLoadingException(ConfigurationException ex)229 public void setLoadingException(ConfigurationException ex) { 230 loadingException = ex; 231 } 232 getLoadingException()233 public ConfigurationException getLoadingException() { 234 return loadingException; 235 } 236 resetToDefaults()237 public void resetToDefaults() { 238 currentConfiguration = Defaults.getDefaults(); 239 } 240 241 242 public enum ConfigType { 243 System, User 244 } 245 246 /** is it mandatory to load the system properties? */ 247 private boolean systemPropertiesMandatory = false; 248 249 /** The system's subdirResult deployment.config file */ 250 private File systemPropertiesFile = null; 251 /** Source of always right and only path to file (even if underlying path changes) */ 252 private final InfrastructureFileDescriptor userDeploymentFileDescriptor; 253 /** The user's subdirResult deployment.config file */ 254 private File userPropertiesFile = null; 255 256 /** the current deployment properties */ 257 private Map<String, Setting<String>> currentConfiguration; 258 259 /** the deployment properties that cannot be changed */ 260 private Map<String, Setting<String>> unchangeableConfiguration; 261 DeploymentConfiguration()262 public DeploymentConfiguration() { 263 this(PathsAndFiles.USER_DEPLOYMENT_FILE); 264 } 265 DeploymentConfiguration(InfrastructureFileDescriptor configFile)266 public DeploymentConfiguration(InfrastructureFileDescriptor configFile) { 267 userDeploymentFileDescriptor = configFile; 268 currentConfiguration = new HashMap<>(); 269 unchangeableConfiguration = new HashMap<>(); 270 } 271 272 /** 273 * Initialize this deployment configuration by reading configuration files. 274 * Generally, it will try to continue and ignore errors it finds (such as file not found). 275 * 276 * @throws ConfigurationException if it encounters a fatal error. 277 */ load()278 public void load() throws ConfigurationException { 279 load(true); 280 } 281 282 /** 283 * Initialize this deployment configuration by reading configuration files. 284 * Generally, it will try to continue and ignore errors it finds (such as file not found). 285 * 286 * @param fixIssues If true, fix issues that are discovered when reading configuration by 287 * resorting to the default values 288 * @throws ConfigurationException if it encounters a fatal error. 289 */ load(boolean fixIssues)290 public void load(boolean fixIssues) throws ConfigurationException { 291 SecurityManager sm = System.getSecurityManager(); 292 if (sm != null) { 293 sm.checkRead(userDeploymentFileDescriptor.getFullPath()); 294 } 295 296 File systemConfigFile = findSystemConfigFile(); 297 298 load(systemConfigFile, userDeploymentFileDescriptor.getFile(), fixIssues); 299 } 300 load(File systemConfigFile, File userFile, boolean fixIssues)301 void load(File systemConfigFile, File userFile, boolean fixIssues) throws ConfigurationException { 302 Map<String, Setting<String>> initialProperties = Defaults.getDefaults(); 303 304 Map<String, Setting<String>> systemProperties = null; 305 306 /* 307 * First, try to read the system's subdirResult deployment.config file to find if 308 * there is a system-level deployment.poperties file 309 */ 310 311 if (systemConfigFile != null) { 312 if (loadSystemConfiguration(systemConfigFile)) { 313 OutputController.getLogger().log("System level " + DEPLOYMENT_CONFIG_FILE + " is mandatory: " + systemPropertiesMandatory); 314 /* Second, read the System level deployment.properties file */ 315 systemProperties = loadProperties(ConfigType.System, systemPropertiesFile, 316 systemPropertiesMandatory); 317 systemComments=loadComments(systemPropertiesFile); 318 } 319 if (systemProperties != null) { 320 mergeMaps(initialProperties, systemProperties); 321 } 322 } 323 324 /* need a copy of the original when we have to save */ 325 unchangeableConfiguration = new HashMap<>(); 326 Set<String> keys = initialProperties.keySet(); 327 for (String key : keys) { 328 unchangeableConfiguration.put(key, new Setting<>(initialProperties.get(key))); 329 } 330 331 /* 332 * Third, read the user's subdirResult deployment.properties file 333 */ 334 userPropertiesFile = userFile; 335 Map<String, Setting<String>> userProperties = loadProperties(ConfigType.User, userPropertiesFile, false); 336 userComments=loadComments(userPropertiesFile); 337 if (userProperties != null) { 338 mergeMaps(initialProperties, userProperties); 339 } 340 341 if (fixIssues) { 342 checkAndFixConfiguration(initialProperties); 343 } 344 345 currentConfiguration = initialProperties; 346 } 347 348 /** 349 * Copies the current configuration into the target 350 * @param target properties where to copy actual ones 351 */ copyTo(Properties target)352 public void copyTo(Properties target) { 353 Set<String> names = getAllPropertyNames(); 354 355 for (String name : names) { 356 String value = getProperty(name); 357 // for Properties, missing and null are identical 358 if (value != null) { 359 target.setProperty(name, value); 360 } 361 } 362 } 363 364 /** 365 * Get the value for the given key 366 * 367 * @param key the property key 368 * @return the value for the key, or null if it can not be found 369 */ getProperty(String key)370 public String getProperty(String key) { 371 SecurityManager sm = System.getSecurityManager(); 372 if (sm != null) { 373 if (userPropertiesFile != null) { 374 sm.checkRead(userPropertiesFile.toString()); 375 } 376 } 377 378 String value = null; 379 if (currentConfiguration.get(key) != null) { 380 value = currentConfiguration.get(key).getValue(); 381 } 382 return value; 383 } 384 385 /** 386 * @return a Set containing all the property names 387 */ getAllPropertyNames()388 public Set<String> getAllPropertyNames() { 389 SecurityManager sm = System.getSecurityManager(); 390 if (sm != null) { 391 if (userPropertiesFile != null) { 392 sm.checkRead(userPropertiesFile.toString()); 393 } 394 } 395 396 return currentConfiguration.keySet(); 397 } 398 399 /** 400 * @return a map containing property names and the corresponding settings 401 */ getRaw()402 public Map<String, Setting<String>> getRaw() { 403 SecurityManager sm = System.getSecurityManager(); 404 if (sm != null) { 405 if (userPropertiesFile != null) { 406 sm.checkRead(userPropertiesFile.toString()); 407 } 408 } 409 410 return currentConfiguration; 411 } 412 413 /** 414 * Sets the value of corresponding to the key. If the value has been marked 415 * as locked, it is not changed 416 * 417 * @param key the key 418 * @param value the value to be associated with the key 419 */ setProperty(String key, String value)420 public void setProperty(String key, String value) { 421 SecurityManager sm = System.getSecurityManager(); 422 if (sm != null) { 423 if (userPropertiesFile != null) { 424 sm.checkWrite(userPropertiesFile.toString()); 425 } 426 } 427 428 Setting<String> currentValue = currentConfiguration.get(key); 429 if (currentValue != null) { 430 if (!currentValue.isLocked()) { 431 currentValue.setValue(value); 432 } 433 } else { 434 currentValue = new Setting<>(key, R("Unknown"), false, null, null, value, R("Unknown")); 435 currentConfiguration.put(key, currentValue); 436 } 437 } 438 439 /** 440 * Check that the configuration is valid. If there are invalid values,set 441 * those values to the default values. This is done by using check() 442 * method of the ValueCheker for each setting on the actual value. Fixes 443 * are made in-place. 444 * 445 * @param initial a map representing the initial configuration 446 */ checkAndFixConfiguration(Map<String, Setting<String>> initial)447 public void checkAndFixConfiguration(Map<String, Setting<String>> initial) { 448 449 Map<String, Setting<String>> defaults = Defaults.getDefaults(); 450 451 for (String key : initial.keySet()) { 452 Setting<String> s = initial.get(key); 453 if (!(s.getName().equals(key))) { 454 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("DCInternal", "key " + key + " does not match setting name " + s.getName())); 455 } else if (!defaults.containsKey(key)) { 456 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("DCUnknownSettingWithName", key)); 457 } else { 458 ValueValidator checker = defaults.get(key).getValidator(); 459 if (checker == null) { 460 continue; 461 } 462 463 try { 464 checker.validate(s.getValue()); 465 } catch (IllegalArgumentException e) { 466 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("DCIncorrectValue", key, s.getValue(), checker.getPossibleValues())); 467 s.setValue(s.getDefaultValue()); 468 OutputController.getLogger().log(e); 469 } 470 } 471 } 472 } 473 474 /** 475 * @return the location of system-level deployment.config file, or null if none can be found 476 */ findSystemConfigFile()477 private File findSystemConfigFile() { 478 if (PathsAndFiles.ETC_DEPLOYMENT_CFG.getFile().isFile()) { 479 return PathsAndFiles.ETC_DEPLOYMENT_CFG.getFile(); 480 } 481 482 String jrePath = null; 483 try { 484 Map<String, Setting<String>> tmpProperties = parsePropertiesFile(userDeploymentFileDescriptor.getFile()); 485 Setting<String> jreSetting = tmpProperties.get(KEY_JRE_DIR); 486 if (jreSetting != null) { 487 jrePath = jreSetting.getValue(); 488 } 489 } catch (Exception ex) { 490 OutputController.getLogger().log(ex); 491 } 492 493 File jreFile; 494 if (jrePath != null) { 495 //based on property KEY_JRE_DIR 496 jreFile = new File(jrePath + File.separator + "lib" 497 + File.separator + DEPLOYMENT_CONFIG_FILE); 498 } else { 499 jreFile = PathsAndFiles.JAVA_DEPLOYMENT_PROP_FILE.getFile(); 500 } 501 if (jreFile.isFile()) { 502 return jreFile; 503 } 504 505 return null; 506 } 507 508 /** 509 * Reads the system configuration file and sets the relevant 510 * system-properties related variables 511 */ loadSystemConfiguration(File configFile)512 private boolean loadSystemConfiguration(File configFile) throws ConfigurationException { 513 514 OutputController.getLogger().log("Loading system configuation from: " + configFile); 515 516 Map<String, Setting<String>> systemConfiguration = new HashMap<>(); 517 try { 518 systemConfiguration = parsePropertiesFile(configFile); 519 } catch (IOException e) { 520 OutputController.getLogger().log("No System level " + DEPLOYMENT_CONFIG_FILE + " found."); 521 OutputController.getLogger().log(e); 522 return false; 523 } 524 525 /* 526 * at this point, we have read the system deployment.config file 527 * completely 528 */ 529 String urlString = null; 530 try { 531 Setting<String> urlSettings = systemConfiguration.get("deployment.system.config"); 532 if (urlSettings == null || urlSettings.getValue() == null) { 533 OutputController.getLogger().log("No System level " + DEPLOYMENT_PROPERTIES + " found in "+configFile.getAbsolutePath()); 534 return false; 535 } 536 urlString = urlSettings.getValue(); 537 Setting<String> mandatory = systemConfiguration.get("deployment.system.config.mandatory"); 538 systemPropertiesMandatory = Boolean.valueOf(mandatory == null ? null : mandatory.getValue()); //never null 539 OutputController.getLogger().log("System level settings " + DEPLOYMENT_PROPERTIES + " are mandatory:" + systemPropertiesMandatory); 540 URL url = new URL(urlString); 541 if (url.getProtocol().equals("file")) { 542 systemPropertiesFile = new File(url.getFile()); 543 OutputController.getLogger().log("Using System level" + DEPLOYMENT_PROPERTIES + ": " + systemPropertiesFile); 544 return true; 545 } else { 546 OutputController.getLogger().log("Remote + " + DEPLOYMENT_PROPERTIES + " not supported: " + urlString + "in " + configFile.getAbsolutePath()); 547 return false; 548 } 549 } catch (MalformedURLException e) { 550 OutputController.getLogger().log("Invalid url for " + DEPLOYMENT_PROPERTIES+ ": " + urlString + "in " + configFile.getAbsolutePath()); 551 OutputController.getLogger().log(e); 552 if (systemPropertiesMandatory){ 553 ConfigurationException ce = new ConfigurationException("Invalid url to system properties, which are mandatory"); 554 ce.initCause(e); 555 throw ce; 556 } else { 557 return false; 558 } 559 } 560 } 561 562 /** 563 * Loads the properties file, if one exists 564 * 565 * @param type the ConfigType to load 566 * @param file the File to load Properties from 567 * @param mandatory indicates if reading this file is mandatory 568 * 569 * @throws ConfigurationException if the file is mandatory but cannot be read 570 */ loadProperties(ConfigType type, File file, boolean mandatory)571 private Map<String, Setting<String>> loadProperties(ConfigType type, File file, boolean mandatory) 572 throws ConfigurationException { 573 if (file == null || !file.isFile()) { 574 OutputController.getLogger().log("No " + type.toString() + " level " + DEPLOYMENT_PROPERTIES + " found."); 575 if (!mandatory) { 576 return null; 577 } else { 578 throw new ConfigurationException(); 579 } 580 } 581 582 OutputController.getLogger().log("Loading " + type.toString() + " level properties from: " + file); 583 try { 584 return parsePropertiesFile(file); 585 } catch (IOException e) { 586 if (mandatory){ 587 ConfigurationException ce = new ConfigurationException("Exception during loading of " + file + " which is mandatory to read"); 588 ce.initCause(e); 589 throw ce; 590 } 591 OutputController.getLogger().log(e); 592 return null; 593 } 594 } 595 596 /** 597 * Saves all properties that are not part of default or system properties 598 * 599 * @throws IOException if unable to save the file 600 * @throws IllegalStateException if save() is called before load() 601 */ save()602 public void save() throws IOException { 603 if (userPropertiesFile == null) { 604 throw new IllegalStateException("must load() before save()"); 605 } 606 607 SecurityManager sm = System.getSecurityManager(); 608 if (sm != null) { 609 sm.checkWrite(userPropertiesFile.toString()); 610 } 611 612 OutputController.getLogger().log("Saving properties into " + userPropertiesFile.toString()); 613 Properties toSave = new Properties(); 614 615 for (String key : currentConfiguration.keySet()) { 616 String oldValue = unchangeableConfiguration.get(key) == null ? null 617 : unchangeableConfiguration.get(key).getValue(); 618 String newValue = currentConfiguration.get(key) == null ? null : currentConfiguration 619 .get(key).getValue(); 620 if (oldValue == null && newValue == null) { 621 continue; 622 } else if (oldValue == null && newValue != null) { 623 toSave.setProperty(key, newValue); 624 } else if (oldValue != null && newValue == null) { 625 toSave.setProperty(key, newValue); 626 } else { // oldValue != null && newValue != null 627 if (!oldValue.equals(newValue)) { 628 toSave.setProperty(key, newValue); 629 } 630 } 631 } 632 633 File backupPropertiesFile = new File(userPropertiesFile.toString() + ".old"); 634 if (userPropertiesFile.isFile()) { 635 if (!userPropertiesFile.renameTo(backupPropertiesFile)) { 636 throw new IOException("Error saving backup copy of " + userPropertiesFile); 637 } 638 } 639 640 FileUtils.createParentDir(userPropertiesFile); 641 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(userPropertiesFile))) { 642 String comments = DEPLOYMENT_COMMENT; 643 if (userComments.length() > 0) { 644 comments = comments + System.lineSeparator() + userComments; 645 } 646 toSave.store(out, comments); ; 647 } 648 } 649 650 /** 651 * Reads a properties file and returns a map representing the properties 652 * 653 * @param propertiesFile the file to read Properties from 654 * @throws IOException if an IO problem occurs 655 */ parsePropertiesFile(File propertiesFile)656 private Map<String, Setting<String>> parsePropertiesFile(File propertiesFile) throws IOException { 657 Map<String, Setting<String>> result = new HashMap<>(); 658 659 Properties properties = new Properties(); 660 661 try (Reader reader = new BufferedReader(new FileReader(propertiesFile))) { 662 properties.load(reader); 663 } 664 665 Set<String> keys = properties.stringPropertyNames(); 666 for (String key : keys) { 667 if (key.endsWith(".locked")) { 668 String realKey = key.substring(0, key.length() - ".locked".length()); 669 Setting<String> configValue = result.get(realKey); 670 if (configValue == null) { 671 configValue = new Setting<>(realKey, R("Unknown"), true, null, null, null, propertiesFile.toString()); 672 result.put(realKey, configValue); 673 } else { 674 configValue.setLocked(true); 675 } 676 } else { 677 /* when parsing a properties we set value without checking if it is locked or not */ 678 String newValue = properties.getProperty(key); 679 Setting<String> configValue = result.get(key); 680 if (configValue == null) { 681 configValue = new Setting<>(key, R("Unknown"), false, null, null, newValue, propertiesFile.toString()); 682 result.put(key, configValue); 683 } else { 684 configValue.setValue(newValue); 685 configValue.setSource(propertiesFile.toString()); 686 } 687 } 688 } 689 return result; 690 } 691 692 /** 693 * Merges two maps while respecting whether the values have been locked or 694 * not. All values from srcMap are put into finalMap, replacing values in 695 * finalMap if necessary, unless the value is present and marked as locked 696 * in finalMap 697 * 698 * @param finalMap the destination for putting values 699 * @param srcMap the source for reading key value pairs 700 */ mergeMaps(Map<String, Setting<String>> finalMap, Map<String, Setting<String>> srcMap)701 private void mergeMaps(Map<String, Setting<String>> finalMap, Map<String, Setting<String>> srcMap) { 702 for (String key : srcMap.keySet()) { 703 Setting<String> destValue = finalMap.get(key); 704 Setting<String> srcValue = srcMap.get(key); 705 if (destValue == null) { 706 finalMap.put(key, srcValue); 707 } else { 708 if (!destValue.isLocked()) { 709 destValue.setSource(srcValue.getSource()); 710 destValue.setValue(srcValue.getValue()); 711 } 712 } 713 } 714 } 715 716 /** 717 * Dumps the configuration to the PrintStream 718 * 719 * @param config a map of key,value pairs representing the configuration to 720 * dump 721 * @param out the PrintStream to write data to 722 */ 723 @SuppressWarnings("unused") dumpConfiguration(Map<String, Setting<String>> config, PrintStream out)724 private static void dumpConfiguration(Map<String, Setting<String>> config, PrintStream out) { 725 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "KEY: VALUE [Locked]"); 726 727 for (String key : config.keySet()) { 728 Setting<String> value = config.get(key); 729 out.println("'" + key + "': '" + value.getValue() + "'" 730 + (value.isLocked() ? " [LOCKED]" : "")); 731 } 732 } 733 move14AndOlderFilesTo15StructureCatched()734 public static void move14AndOlderFilesTo15StructureCatched() { 735 try { 736 move14AndOlderFilesTo15Structure(); 737 } catch (Throwable t) { 738 OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Critical error during converting old files to new. Continuing"); 739 OutputController.getLogger().log(t); 740 } 741 742 } 743 move14AndOlderFilesTo15Structure()744 private static void move14AndOlderFilesTo15Structure() { 745 int errors = 0; 746 String PRE_15_DEPLOYMENT_DIR = ".icedtea"; 747 String LEGACY_USER_HOME = System.getProperty("user.home") + File.separator + PRE_15_DEPLOYMENT_DIR; 748 String legacyProperties = LEGACY_USER_HOME + File.separator + DEPLOYMENT_PROPERTIES; 749 File configDir = new File(PathsAndFiles.USER_CONFIG_HOME); 750 File cacheDir = new File(PathsAndFiles.USER_CACHE_HOME); 751 File legacyUserDir = new File(LEGACY_USER_HOME); 752 if (legacyUserDir.exists()) { 753 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, TRANSFER_TITLE); 754 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, PathsAndFiles.USER_CONFIG_HOME + " and " + PathsAndFiles.USER_CACHE_HOME); 755 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "You should not see this message next time you run icedtea-web!"); 756 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Your custom dirs will not be touched and will work"); 757 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "-----------------------------------------------"); 758 759 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Preparing new directories:"); 760 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + PathsAndFiles.USER_CONFIG_HOME); 761 errors += resultToStd(configDir.mkdirs()); 762 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + PathsAndFiles.USER_CACHE_HOME); 763 errors += resultToStd(cacheDir.mkdirs()); 764 //move this first, the creation of config singleton may happen anytime... 765 //but must not before USER_DEPLOYMENT_FILE is moved and should not in this block 766 String currentProperties = PathsAndFiles.USER_DEPLOYMENT_FILE.getDefaultFullPath(); 767 errors += moveLegacyToCurrent(legacyProperties, currentProperties); 768 769 String legacyPropertiesOld = LEGACY_USER_HOME + File.separator + DEPLOYMENT_PROPERTIES + ".old"; 770 String currentPropertiesOld = currentProperties + ".old"; 771 errors += moveLegacyToCurrent(legacyPropertiesOld, currentPropertiesOld); 772 773 String legacySecurity = LEGACY_USER_HOME + File.separator + "security"; 774 String currentSecurity = PathsAndFiles.USER_DEFAULT_SECURITY_DIR; 775 errors += moveLegacyToCurrent(legacySecurity, currentSecurity); 776 777 String legacyAppletTrust = LEGACY_USER_HOME + File.separator + APPLET_TRUST_SETTINGS; 778 String currentAppletTrust = PathsAndFiles.APPLET_TRUST_SETTINGS_USER.getDefaultFullPath(); 779 errors += moveLegacyToCurrent(legacyAppletTrust, currentAppletTrust); 780 781 //note - all here use default path. Any call to getFullPAth will invoke creation of config singleton 782 // but: we DO copy only defaults. There is no need to copy nondefaults! 783 // nond-efault will be used thanks to config singleton read from copied deployment.properties 784 785 String legacyCache = LEGACY_USER_HOME + File.separator + "cache"; 786 String currentCache = PathsAndFiles.CACHE_DIR.getDefaultFullPath(); 787 errors += moveLegacyToCurrent(legacyCache, currentCache); 788 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Adapting " + PathsAndFiles.CACHE_INDEX_FILE_NAME + " to new destination"); 789 //replace all legacyCache by currentCache in new recently_used 790 try { 791 File f = PathsAndFiles.getRecentlyUsedFile().getDefaultFile(); 792 String s = FileUtils.loadFileAsString(f); 793 s = s.replace(legacyCache, currentCache); 794 FileUtils.saveFile(s, f); 795 } catch (IOException ex) { 796 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, ex); 797 errors++; 798 } 799 800 String legacyPcahceDir = LEGACY_USER_HOME + File.separator + "pcache"; 801 String currentPcacheDir = PathsAndFiles.PCACHE_DIR.getDefaultFullPath(); 802 errors += moveLegacyToCurrent(legacyPcahceDir, currentPcacheDir); 803 804 String legacyLogDir = LEGACY_USER_HOME + File.separator + "log"; 805 String currentLogDir = PathsAndFiles.LOG_DIR.getDefaultFullPath(); 806 errors += moveLegacyToCurrent(legacyLogDir, currentLogDir); 807 808 String legacyTmp = LEGACY_USER_HOME + File.separator + "tmp"; 809 String currentTmp = PathsAndFiles.TMP_DIR.getDefaultFullPath(); 810 errors += moveLegacyToCurrent(legacyTmp, currentTmp); 811 812 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Removing now empty " + LEGACY_USER_HOME); 813 errors += resultToStd(legacyUserDir.delete()); 814 815 if (errors != 0) { 816 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "There occureed " + errors + " errors"); 817 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Please double check content of old data in " + LEGACY_USER_HOME + " with "); 818 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "new " + PathsAndFiles.USER_CONFIG_HOME + " and " + PathsAndFiles.USER_CACHE_HOME); 819 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "To disable this check again, please remove " + LEGACY_USER_HOME); 820 } 821 822 } else { 823 OutputController.getLogger().log("System is already following XDG .cache and .config specifications"); 824 try { 825 OutputController.getLogger().log("config: " + PathsAndFiles.USER_CONFIG_HOME + " file exists: " + configDir.exists()); 826 } catch (Exception ex) { 827 OutputController.getLogger().log(ex); 828 } 829 try { 830 OutputController.getLogger().log("cache: " + PathsAndFiles.USER_CACHE_HOME + " file exists:" + cacheDir.exists()); 831 } catch (Exception ex) { 832 OutputController.getLogger().log(ex); 833 } 834 } 835 //this call should endure even if (ever) will migration code be removed 836 DirectoryValidator.DirectoryCheckResults r = new DirectoryValidator().ensureDirs(); 837 if (!JNLPRuntime.isHeadless()) { 838 if (r.getFailures() > 0) { 839 JOptionPane.showMessageDialog(null, r.getMessage()); 840 } 841 } 842 843 } 844 moveLegacyToCurrent(String legacy, String current)845 private static int moveLegacyToCurrent(String legacy, String current) { 846 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Moving " + legacy + " to " + current); 847 File cf = new File(current); 848 File old = new File(legacy); 849 if (cf.exists()) { 850 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Warning! Destination " + current + " exists!"); 851 } 852 if (old.exists()) { 853 boolean moved = old.renameTo(cf); 854 return resultToStd(moved); 855 } else { 856 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Source " + legacy + " do not exists, nothing to do"); 857 return 0; 858 } 859 860 } 861 resultToStd(boolean securityMove)862 private static int resultToStd(boolean securityMove) { 863 if (securityMove) { 864 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "OK"); 865 return 0; 866 } else { 867 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "ERROR"); 868 return 1; 869 } 870 } 871 872 //standard date.toString format 873 public static final SimpleDateFormat pattern = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); 874 loadComments(File path)875 private static String loadComments(File path) { 876 StringBuilder r = new StringBuilder(); 877 try (BufferedReader br = new BufferedReader(new FileReader(path))) { 878 while (true) { 879 String s = br.readLine(); 880 if (s == null) { 881 break; 882 } 883 s = s.trim(); 884 if (s.startsWith("#")) { 885 String decommented = s.substring(1); 886 if (decommented.isEmpty()){ 887 continue; 888 } 889 if (decommented.equals(DEPLOYMENT_COMMENT)){ 890 continue; 891 } 892 //there is always also date 893 Date dd = null; 894 try { 895 dd = pattern.parse(decommented); 896 } catch (Exception ex) { 897 //we really dont care, failure is our decision point 898 } 899 if (dd == null){ 900 r.append(decommented).append("\n"); 901 } 902 } 903 } 904 } catch (Exception ex) { 905 OutputController.getLogger().log(ex); 906 } 907 908 return r.toString().trim(); 909 } 910 } 911