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