1 /*
2  * Created on May 18, 2004
3  *
4  * Paros and its related class files.
5  *
6  * Paros is an HTTP/HTTPS proxy for assessing web application security.
7  * Copyright (C) 2003-2005 Chinotec Technologies Company
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the Clarified Artistic License
11  * as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * Clarified Artistic License for more details.
17  *
18  * You should have received a copy of the Clarified Artistic License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  */
22 // ZAP: 2011/08/03 Revamped upgrade for 1.3.2
23 // ZAP: 2011/10/05 Write backup file to user dir
24 // ZAP: 2011/11/15 Changed to use ZapXmlConfiguration, to enforce the same
25 // character encoding when reading/writing configurations. Changed to use the
26 // correct file when an error occurs during the load of the configuration file.
27 // Removed the calls XMLConfiguration.load() as they are not needed, the
28 // XMLConfiguration constructor used already does that.
29 // ZAP: 2011/11/20 Support for extension factory
30 // ZAP: 2012/03/03 Added ZAP homepage
31 // ZAP: 2012/03/15 Removed a @SuppressWarnings annotation from the method
32 // copyAllProperties.
33 // ZAP: 2012/03/17 Issue 282 ZAP and PAROS team constants
34 // ZAP: 2012/05/02 Added method createInstance and changed the method
35 // getInstance to use it.
36 // ZAP: 2012/05/03 Changed the Patterns used to detect the O.S. to be final.
37 // ZAP: 2012/06/15 Issue 312 Increase the maximum number of scanning threads allowed
38 // ZAP: 2012/07/13 Added variable for maximum number of threads used in scan (MAX_THREADS_PER_SCAN)
39 // ZAP: 2012/10/15 Issue 397: Support weekly builds
40 // ZAP: 2012/10/17 Issue 393: Added more online links from menu
41 // ZAP: 2012/11/15 Issue 416: Normalise how multiple related options are managed
42 // throughout ZAP and enhance the usability of some options.
43 // ZAP: 2012/11/20 Issue 419: Restructure jar loading code
44 // ZAP: 2012/12/08 Issue 428: Changed to use I18N for messages, to support the marketplace
45 // ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
46 // ZAP: 2013/04/14 Issue 610: Replace the use of the String class for available/default "Forced
47 // Browse" files
48 // ZAP: 2013/04/15 Issue 632: Manual Request Editor dialogue (HTTP) configurations not saved
49 // correctly
50 // ZAP: 2013/12/03 Issue 933: Automatically determine install dir
51 // ZAP: 2013/12/13 Issue 919: Support for multiple language vulnerability files.
52 // ZAP: 2014/04/11 Issue 1148: ZAP 2.3.0 does not launch after upgrading in some situations
53 // ZAP: 2014/07/15 Issue 1265: Context import and export
54 // ZAP: 2014/08/14 Issue 1300: Add-ons show incorrect language when English is selected on non
55 // English locale
56 // ZAP: 2014/11/11 Issue 1406: Move online menu items to an add-on
57 // ZAP: 2015/01/04 Issue 1388: Not all translated files are updated when "zaplang" package is
58 // imported
59 // ZAP: 2014/01/04 Issue 1394: Import vulnerabilities.xml files when updating the translated
60 // resources
61 // ZAP: 2014/01/04 Issue 1458: Change home/installation dir paths to be always absolute
62 // ZAP: 2015/03/10 Issue 653: Handle updates on Kali better
63 // ZAP: 2015/03/30 Issue 1582: Enablers for low memory option
64 // ZAP: 2015/04/12 Remove "installation" fuzzers dir, no longer in use
65 // ZAP: 2015/08/01 Remove code duplication in catch of exceptions, use installation directory in
66 // default config file
67 // ZAP: 2015/11/11 Issue 2045: Dont copy old configs if -dir option used
68 // ZAP: 2015/11/26 Issue 2084: Warn users if they are probably using out of date versions
69 // ZAP: 2016/02/17 Convert extensions' options to not use extensions' names as XML element names
70 // ZAP: 2016/05/12 Use dev/weekly dir for plugin downloads when copying the existing 'release'
71 // config file
72 // ZAP: 2016/06/07 Remove commented constants and statement that had no (actual) effect, add doc to
73 // a constant and init other
74 // ZAP: 2016/06/07 Use filter directory in ZAP's home directory
75 // ZAP: 2016/06/13 Migrate config option "proxy.modifyAcceptEncoding"
76 // ZAP: 2016/07/07 Convert passive scanners options to new structure
77 // ZAP: 2016/09/22 JavaDoc tweaks
78 // ZAP: 2016/11/17 Issue 2701 Support Factory Reset
79 // ZAP: 2017/05/04 Issue 3440: Log Exception when overwriting a config file
80 // ZAP: 2017/12/26 Remove class methods no longer used.
81 // ZAP: 2018/01/03 No longer create filter dir and deprecate FOLDER_FILTER constant.
82 //                 Exit immediately if not able to create the home dir.
83 // ZAP: 2018/01/04 Clear SNI Terminator options when updating from older ZAP versions.
84 // ZAP: 2018/01/05 Prevent use of install dir as home dir.
85 // ZAP: 2018/02/14 Remove unnecessary boxing / unboxing
86 // ZAP: 2018/03/16 Use equalsIgnoreCase (Issue 4327).
87 // ZAP: 2018/04/16 Keep backup of malformed config file.
88 // ZAP: 2018/06/13 Correct install dir detection from JAR.
89 // ZAP: 2018/06/29 Allow to check if in dev mode.
90 // ZAP: 2018/07/19 Fallback to bundled config.xml and log4j.properties.
91 // ZAP: 2019/03/14 Move and correct update of old options.
92 // ZAP: 2019/04/01 Refactored to reduce code-duplication.
93 // ZAP: 2019/05/10 Apply installer config options on update.
94 //                 Fix exceptions during config update.
95 // ZAP: 2019/05/14 Added silent option
96 // ZAP: 2019/05/17 Update cert option to boolean.
97 // ZAP: 2019/05/29 Update Jericho log configuration.
98 // ZAP: 2019/06/01 Normalise line endings.
99 // ZAP: 2019/06/05 Normalise format/style.
100 // ZAP: 2019/06/07 Update current version.
101 // ZAP: 2019/09/16 Deprecate ZAP_HOMEPAGE and ZAP_EXTENSIONS_PAGE.
102 // ZAP: 2019/11/07 Removed constants related to accepting the license.
103 // ZAP: 2020/01/02 Updated config version and default user agent
104 // ZAP: 2020/01/06 Set latest version to default config.
105 // ZAP: 2020/01/10 Correct the MailTo autoTagScanner regex pattern when upgrading from 2.8 or
106 // earlier.
107 // ZAP: 2020/04/22 Check ControlOverrides when determining the locale.
108 // ZAP: 2020/09/17 Correct the Syntax Highlighting markoccurrences config key name when upgrading
109 // from 2.9 or earlier.
110 // ZAP: 2020/10/07 Changes for Log4j 2 migration.
111 // ZAP: 2020/11/02 Do not backup old Log4j config if already present.
112 // ZAP: 2020/11/26 Use Log4j 2 classes for logging.
113 // ZAP: 2021/09/15 Added support for detecting containers
114 // ZAP: 2021/09/21 Added support for detecting snapcraft
115 // ZAP: 2021/10/01 Added support for detecting WebSwing
116 // ZAP: 2021/10/06 Update user agent when upgrading from 2.10
117 package org.parosproxy.paros;
118 
119 import java.io.File;
120 import java.io.IOException;
121 import java.io.InputStream;
122 import java.net.MalformedURLException;
123 import java.net.URISyntaxException;
124 import java.net.URL;
125 import java.nio.charset.StandardCharsets;
126 import java.nio.file.FileSystems;
127 import java.nio.file.Files;
128 import java.nio.file.Path;
129 import java.nio.file.Paths;
130 import java.nio.file.StandardCopyOption;
131 import java.security.InvalidParameterException;
132 import java.text.ParseException;
133 import java.text.SimpleDateFormat;
134 import java.util.ArrayList;
135 import java.util.Date;
136 import java.util.HashSet;
137 import java.util.Iterator;
138 import java.util.List;
139 import java.util.Locale;
140 import java.util.NoSuchElementException;
141 import java.util.Properties;
142 import java.util.Set;
143 import java.util.jar.Attributes;
144 import java.util.jar.Manifest;
145 import java.util.regex.Matcher;
146 import java.util.regex.Pattern;
147 import org.apache.commons.configuration.ConfigurationException;
148 import org.apache.commons.configuration.ConversionException;
149 import org.apache.commons.configuration.HierarchicalConfiguration;
150 import org.apache.commons.configuration.XMLConfiguration;
151 import org.apache.logging.log4j.LogManager;
152 import org.apache.logging.log4j.Logger;
153 import org.apache.logging.log4j.core.config.Configurator;
154 import org.parosproxy.paros.extension.option.OptionsParamView;
155 import org.parosproxy.paros.model.FileCopier;
156 import org.parosproxy.paros.model.Model;
157 import org.parosproxy.paros.network.ConnectionParam;
158 import org.zaproxy.zap.ZAP;
159 import org.zaproxy.zap.control.AddOnLoader;
160 import org.zaproxy.zap.control.ControlOverrides;
161 import org.zaproxy.zap.extension.autoupdate.OptionsParamCheckForUpdates;
162 import org.zaproxy.zap.utils.I18N;
163 import org.zaproxy.zap.utils.ZapXmlConfiguration;
164 
165 public final class Constant {
166     // ZAP: rebrand
167     public static final String PROGRAM_NAME = "OWASP ZAP";
168     public static final String PROGRAM_NAME_SHORT = "ZAP";
169     /** @deprecated (2.9.0) Do not use, it will be removed. */
170     @Deprecated public static final String ZAP_HOMEPAGE = "http://www.owasp.org/index.php/ZAP";
171     /** @deprecated (2.9.0) Do not use, it will be removed. */
172     @Deprecated
173     public static final String ZAP_EXTENSIONS_PAGE = "https://github.com/zaproxy/zap-extensions";
174 
175     public static final String ZAP_TEAM = "ZAP Dev Team";
176     public static final String PAROS_TEAM = "Chinotec Technologies";
177 
178     //  ************************************************************
179     //  the config.xml MUST be set to be the same as the version_tag
180     //  otherwise the config.xml will be overwritten everytime.
181     //  ************************************************************
182     private static final String DEV_VERSION = "Dev Build";
183     public static final String ALPHA_VERSION = "alpha";
184     public static final String BETA_VERSION = "beta";
185 
186     private static final String VERSION_ELEMENT = "version";
187 
188     // Accessible for tests
189     static final long VERSION_TAG = 20011001;
190 
191     // Old version numbers - for upgrade
192     private static final long V_2_10_0_TAG = 20010000;
193     private static final long V_2_9_0_TAG = 2009000;
194     private static final long V_2_8_0_TAG = 2008000;
195     private static final long V_2_7_0_TAG = 2007000;
196     private static final long V_2_5_0_TAG = 2005000;
197     private static final long V_2_4_3_TAG = 2004003;
198     private static final long V_2_3_1_TAG = 2003001;
199     private static final long V_2_2_2_TAG = 2002002;
200     private static final long V_2_2_0_TAG = 2002000;
201     private static final long V_2_1_0_TAG = 2001000;
202     private static final long V_2_0_0_TAG = 2000000;
203     private static final long V_1_4_1_TAG = 1004001;
204     private static final long V_1_3_1_TAG = 1003001;
205     private static final long V_1_3_0_TAG = 1003000;
206     private static final long V_1_2_1_TAG = 1002001;
207     private static final long V_1_2_0_TAG = 1002000;
208     private static final long V_1_1_0_TAG = 1001000;
209     private static final long V_1_0_0_TAG = 1000000;
210     private static final long V_PAROS_TAG = 30020013;
211 
212     //  ************************************************************
213     //  note the above
214     //  ************************************************************
215 
216     // These are no longer final - version is now loaded from the manifest file
217     public static String PROGRAM_VERSION = DEV_VERSION;
218     public static String PROGRAM_TITLE = PROGRAM_NAME + " " + PROGRAM_VERSION;
219 
220     public static final String SYSTEM_PAROS_USER_LOG = "zap.user.log";
221 
222     public static final String FILE_SEPARATOR = System.getProperty("file.separator");
223 
224     /**
225      * @deprecated (2.4.2) The path does not take into account the installation directory, use
226      *     {@link #getPathDefaultConfigFile()} instead.
227      */
228     @Deprecated public static final String FILE_CONFIG_DEFAULT = "xml/config.xml";
229 
230     public static final String FILE_CONFIG_NAME = "config.xml";
231     public static final String FOLDER_PLUGIN = "plugin";
232     /**
233      * The name of the directory for filter related files (the path should be built using {@link
234      * #getZapHome()} as the parent directory).
235      *
236      * @deprecated (2.8.0) Should not be used, the filter functionality is deprecated (replaced by
237      *     scripts and Replacer add-on).
238      * @since 1.0.0
239      */
240     @Deprecated public static final String FOLDER_FILTER = "filter";
241 
242     /**
243      * The name of the directory where the (file) sessions are saved by default.
244      *
245      * @since 1.0.0
246      */
247     public static final String FOLDER_SESSION_DEFAULT = "session";
248 
249     public static final String DBNAME_TEMPLATE =
250             "db" + System.getProperty("file.separator") + "zapdb";
251 
252     /**
253      * Prefix (file name) of Messages.properties files.
254      *
255      * @see #MESSAGES_EXTENSION
256      */
257     public static final String MESSAGES_PREFIX = "Messages";
258 
259     /**
260      * Extension (with dot) of Messages.properties files.
261      *
262      * @see #MESSAGES_PREFIX
263      * @since 2.4.0
264      */
265     public static final String MESSAGES_EXTENSION = ".properties";
266 
267     public static final String DBNAME_UNTITLED_DEFAULT =
268             FOLDER_SESSION_DEFAULT + System.getProperty("file.separator") + "untitled";
269 
270     public String FILE_CONFIG = FILE_CONFIG_NAME;
271     public String FOLDER_SESSION = FOLDER_SESSION_DEFAULT;
272     public String DBNAME_UNTITLED =
273             FOLDER_SESSION + System.getProperty("file.separator") + "untitled";
274 
275     public static final String FILE_PROGRAM_SPLASH = "resource/zap128x128.png";
276 
277     // Accelerator keys - Default: Windows
278     public static String ACCELERATOR_UNDO = "control Z";
279     public static String ACCELERATOR_REDO = "control Y";
280     public static String ACCELERATOR_TRIGGER_KEY = "Control";
281 
282     private static Constant instance = null;
283 
284     public static final int MAX_HOST_CONNECTION = 15;
285     public static final int MAX_THREADS_PER_SCAN = 50;
286     // ZAP: Dont announce ourselves
287     // public static final String USER_AGENT = PROGRAM_NAME + "/" + PROGRAM_VERSION;
288     public static final String USER_AGENT = "";
289 
290     private static String staticEyeCatcher = "0W45pz4p";
291 
292     private static final String USER_CONTEXTS_DIR = "contexts";
293     private static final String USER_POLICIES_DIR = "policies";
294 
295     private static final String ZAP_CONTAINER_FILE = "/zap/container";
296     private static final String FLATPAK_FILE = "/.flatpak-info";
297     public static final String FLATPAK_NAME = "flatpak";
298     private static final String SNAP_FILE = "meta/snap.yaml";
299     public static final String SNAP_NAME = "snapcraft";
300     private static final String WEBSWING_ENVVAR = "WEBSWING_VERSION";
301     public static final String WEBSWING_NAME = "webswing";
302 
303     //
304     // Home dir for ZAP, i.e. where the config file is. Can be set on cmdline, otherwise will be set
305     // to default loc
306     private static String zapHome = null;
307     // Default home dir for 'full' releases - used for copying full conf file when dev/daily release
308     // run for the first time
309     // and also for the JVM options config file
310     private static String zapStd = null;
311     // Install dir for ZAP, but default will be cwd
312     private static String zapInstall = null;
313 
314     private static Boolean onKali = null;
315     private static Boolean inContainer = null;
316     private static String containerName;
317     private static Boolean lowMemoryOption = null;
318 
319     // ZAP: Added i18n
320     public static I18N messages = null;
321 
322     /**
323      * The system's locale (as determined by the JVM at startup, {@code Locale#getDefault()}).
324      *
325      * <p>The locale is kept here because the default locale is later overridden with the user's
326      * chosen locale/language.
327      *
328      * @see Locale#getDefault()
329      */
330     private static final Locale SYSTEMS_LOCALE = Locale.getDefault();
331 
332     /** The path to bundled (in zap.jar) config.xml file. */
333     private static final String PATH_BUNDLED_CONFIG_XML =
334             "/org/zaproxy/zap/resources/" + FILE_CONFIG_NAME;
335 
336     /**
337      * Name of directory that contains the (source and translated) resource files.
338      *
339      * @see #MESSAGES_PREFIX
340      * @see #VULNERABILITIES_PREFIX
341      */
342     public static final String LANG_DIR = "lang";
343 
344     /**
345      * Prefix (file name) of vulnerabilities.xml files.
346      *
347      * @see #VULNERABILITIES_EXTENSION
348      * @since 2.4.0
349      */
350     public static final String VULNERABILITIES_PREFIX = "vulnerabilities";
351 
352     /**
353      * @deprecated (2.4.0) Use {@link #VULNERABILITIES_PREFIX} instead. It will be removed in a
354      *     following release.
355      */
356     @Deprecated public static String VULNS_BASE = VULNERABILITIES_PREFIX;
357 
358     /**
359      * Extension (with dot) of vulnerabilities.xml files.
360      *
361      * @see #VULNERABILITIES_PREFIX
362      * @since 2.4.0
363      */
364     public static final String VULNERABILITIES_EXTENSION = ".xml";
365 
366     /**
367      * Flag that indicates whether or not the "dev mode" is enabled.
368      *
369      * @see #isDevMode()
370      */
371     private static boolean devMode;
372 
373     private static boolean silent;
374 
375     // ZAP: Added dirbuster dir
376     public String DIRBUSTER_DIR = "dirbuster";
377     public String DIRBUSTER_CUSTOM_DIR = DIRBUSTER_DIR;
378 
379     public String FUZZER_DIR = "fuzzers";
380 
381     public static String FOLDER_LOCAL_PLUGIN = FOLDER_PLUGIN;
382 
383     public static final URL OK_FLAG_IMAGE_URL =
384             Constant.class.getResource("/resource/icon/10/072.png"); // Green
385     public static final URL INFO_FLAG_IMAGE_URL =
386             Constant.class.getResource("/resource/icon/10/073.png"); // Blue
387     public static final URL LOW_FLAG_IMAGE_URL =
388             Constant.class.getResource("/resource/icon/10/074.png"); // Yellow
389     public static final URL MED_FLAG_IMAGE_URL =
390             Constant.class.getResource("/resource/icon/10/076.png"); // Orange
391     public static final URL HIGH_FLAG_IMAGE_URL =
392             Constant.class.getResource("/resource/icon/10/071.png"); // Red
393     public static final URL BLANK_IMAGE_URL =
394             Constant.class.getResource("/resource/icon/10/blank.png");
395     public static final URL SPIDER_IMAGE_URL =
396             Constant.class.getResource("/resource/icon/10/spider.png");
397 
398     private static Logger LOG = LogManager.getLogger(Constant.class);
399 
getEyeCatcher()400     public static String getEyeCatcher() {
401         return staticEyeCatcher;
402     }
403 
setEyeCatcher(String eyeCatcher)404     public static void setEyeCatcher(String eyeCatcher) {
405         staticEyeCatcher = eyeCatcher;
406     }
407 
Constant()408     public Constant() {
409         this(null);
410     }
411 
Constant(ControlOverrides overrides)412     private Constant(ControlOverrides overrides) {
413         initializeFilesAndDirectories(overrides);
414         setAcceleratorKeys();
415     }
416 
getDefaultHomeDirectory(boolean incDevOption)417     public static String getDefaultHomeDirectory(boolean incDevOption) {
418         if (zapStd == null) {
419             zapStd = System.getProperty("user.home");
420             if (zapStd == null) {
421                 zapStd = ".";
422             }
423 
424             if (isLinux()) {
425                 // Linux: Hidden Zap directory in the user's home directory
426                 zapStd += FILE_SEPARATOR + "." + PROGRAM_NAME_SHORT;
427             } else if (isMacOsX()) {
428                 // Mac Os X: Support for writing the configuration into the users Library
429                 zapStd +=
430                         FILE_SEPARATOR
431                                 + "Library"
432                                 + FILE_SEPARATOR
433                                 + "Application Support"
434                                 + FILE_SEPARATOR
435                                 + PROGRAM_NAME_SHORT;
436             } else {
437                 // Windows: Zap directory in the user's home directory
438                 zapStd += FILE_SEPARATOR + PROGRAM_NAME;
439             }
440         }
441 
442         if (incDevOption) {
443             if (isDevMode() || isDailyBuild()) {
444                 // Default to a different home dir to prevent messing up full releases
445                 return zapStd + "_D";
446             }
447         }
448         return zapStd;
449     }
450 
copyDefaultConfigs(File f, boolean forceReset)451     public void copyDefaultConfigs(File f, boolean forceReset)
452             throws IOException, ConfigurationException {
453         FileCopier copier = new FileCopier();
454         File oldf;
455         if (isDevMode() || isDailyBuild()) {
456             // try standard location
457             oldf = new File(getDefaultHomeDirectory(false) + FILE_SEPARATOR + FILE_CONFIG_NAME);
458         } else {
459             // try old location
460             oldf = new File(zapHome + FILE_SEPARATOR + "zap" + FILE_SEPARATOR + FILE_CONFIG_NAME);
461         }
462 
463         if (!forceReset
464                 && oldf.exists()
465                 && Paths.get(zapHome).equals(Paths.get(getDefaultHomeDirectory(true)))) {
466             // Dont copy old configs if forcedReset or they've specified a non std directory
467             LOG.info("Copying defaults from " + oldf.getAbsolutePath() + " to " + FILE_CONFIG);
468             copier.copy(oldf, f);
469 
470             if (isDevMode() || isDailyBuild()) {
471                 ZapXmlConfiguration newConfig = new ZapXmlConfiguration(f);
472                 newConfig.setProperty(
473                         OptionsParamCheckForUpdates.DOWNLOAD_DIR, Constant.FOLDER_LOCAL_PLUGIN);
474                 newConfig.save();
475             }
476         } else {
477             LOG.info("Copying default configuration to " + FILE_CONFIG);
478             copyDefaultConfigFile();
479         }
480     }
481 
copyDefaultConfigFile()482     private void copyDefaultConfigFile() throws IOException {
483         Path configFile = Paths.get(FILE_CONFIG);
484         copyFileToHome(configFile, "xml/" + FILE_CONFIG_NAME, PATH_BUNDLED_CONFIG_XML);
485         try {
486             setLatestVersion(new ZapXmlConfiguration(configFile.toFile()));
487         } catch (ConfigurationException e) {
488             throw new IOException("Failed to set the latest version:", e);
489         }
490     }
491 
492     /**
493      * Sets the latest version ({@link #VERSION_TAG}) to the given configuration and then saves it.
494      *
495      * @param config the configuration to change
496      * @throws ConfigurationException if an error occurred while saving the configuration.
497      */
setLatestVersion(XMLConfiguration config)498     private static void setLatestVersion(XMLConfiguration config) throws ConfigurationException {
499         config.setProperty(VERSION_ELEMENT, VERSION_TAG);
500         config.save();
501     }
502 
copyFileToHome( Path targetFile, String sourceFilePath, String fallbackResource)503     private static void copyFileToHome(
504             Path targetFile, String sourceFilePath, String fallbackResource) throws IOException {
505         Path defaultConfig = Paths.get(getZapInstall(), sourceFilePath);
506         if (Files.exists(defaultConfig)) {
507             Files.copy(defaultConfig, targetFile, StandardCopyOption.REPLACE_EXISTING);
508         } else {
509             try (InputStream is = Constant.class.getResourceAsStream(fallbackResource)) {
510                 if (is == null) {
511                     throw new IOException("Bundled resource not found: " + fallbackResource);
512                 }
513                 Files.copy(is, targetFile, StandardCopyOption.REPLACE_EXISTING);
514             }
515         }
516     }
517 
getUrlDefaultConfigFile()518     private static URL getUrlDefaultConfigFile() {
519         Path path = getPathDefaultConfigFile();
520         if (Files.exists(path)) {
521             try {
522                 return path.toUri().toURL();
523             } catch (MalformedURLException e) {
524                 LOG.debug("Failed to convert file system path:", e);
525             }
526         }
527         return Constant.class.getResource(PATH_BUNDLED_CONFIG_XML);
528     }
529 
initializeFilesAndDirectories()530     public void initializeFilesAndDirectories() {
531         initializeFilesAndDirectories(null);
532     }
533 
initializeFilesAndDirectories(ControlOverrides overrides)534     private void initializeFilesAndDirectories(ControlOverrides overrides) {
535 
536         FileCopier copier = new FileCopier();
537         File f = null;
538 
539         // Set up the version from the manifest
540         PROGRAM_VERSION = getVersionFromManifest();
541         PROGRAM_TITLE = PROGRAM_NAME + " " + PROGRAM_VERSION;
542 
543         if (zapHome == null) {
544             zapHome = getDefaultHomeDirectory(true);
545         }
546 
547         zapHome = getAbsolutePath(zapHome);
548         f = new File(zapHome);
549 
550         FILE_CONFIG = zapHome + FILE_CONFIG;
551         FOLDER_SESSION = zapHome + FOLDER_SESSION;
552         DBNAME_UNTITLED = zapHome + DBNAME_UNTITLED;
553         DIRBUSTER_CUSTOM_DIR = zapHome + DIRBUSTER_DIR;
554         FUZZER_DIR = zapHome + FUZZER_DIR;
555         FOLDER_LOCAL_PLUGIN = zapHome + FOLDER_PLUGIN;
556 
557         try {
558             System.setProperty(SYSTEM_PAROS_USER_LOG, zapHome);
559 
560             if (!f.isDirectory()) {
561                 if (f.exists()) {
562                     System.err.println("The home path is not a directory: " + zapHome);
563                     System.exit(1);
564                 }
565                 if (!f.mkdir()) {
566                     System.err.println("Unable to create home directory: " + zapHome);
567                     System.err.println("Is the path correct and there's write permission?");
568                     System.exit(1);
569                 }
570             } else if (!f.canWrite()) {
571                 System.err.println("The home path is not writable: " + zapHome);
572                 System.exit(1);
573             } else {
574                 Path installDir = Paths.get(getZapInstall()).toRealPath();
575                 if (installDir.equals(Paths.get(zapHome).toRealPath())) {
576                     System.err.println(
577                             "The install dir should not be used as home dir: " + installDir);
578                     System.exit(1);
579                 }
580             }
581 
582             setUpLogging();
583 
584             f = new File(FILE_CONFIG);
585             if (!f.isFile()) {
586                 this.copyDefaultConfigs(f, false);
587             }
588 
589             f = new File(FOLDER_SESSION);
590             if (!f.isDirectory()) {
591                 LOG.info("Creating directory " + FOLDER_SESSION);
592                 if (!f.mkdir()) {
593                     // ZAP: report failure to create directory
594                     System.out.println("Failed to create directory " + f.getAbsolutePath());
595                 }
596             }
597             f = new File(DIRBUSTER_CUSTOM_DIR);
598             if (!f.isDirectory()) {
599                 LOG.info("Creating directory " + DIRBUSTER_CUSTOM_DIR);
600                 if (!f.mkdir()) {
601                     // ZAP: report failure to create directory
602                     System.out.println("Failed to create directory " + f.getAbsolutePath());
603                 }
604             }
605             f = new File(FUZZER_DIR);
606             if (!f.isDirectory()) {
607                 LOG.info("Creating directory " + FUZZER_DIR);
608                 if (!f.mkdir()) {
609                     // ZAP: report failure to create directory
610                     System.out.println("Failed to create directory " + f.getAbsolutePath());
611                 }
612             }
613             f = new File(FOLDER_LOCAL_PLUGIN);
614             if (!f.isDirectory()) {
615                 LOG.info("Creating directory " + FOLDER_LOCAL_PLUGIN);
616                 if (!f.mkdir()) {
617                     // ZAP: report failure to create directory
618                     System.out.println("Failed to create directory " + f.getAbsolutePath());
619                 }
620             }
621 
622         } catch (Exception e) {
623             System.err.println("Unable to initialize home directory! " + e.getMessage());
624             e.printStackTrace(System.err);
625             System.exit(1);
626         }
627 
628         // Upgrade actions
629         try {
630             try {
631 
632                 // ZAP: Changed to use ZapXmlConfiguration, to enforce the same character encoding
633                 // when reading/writing configurations.
634                 XMLConfiguration config = new ZapXmlConfiguration(FILE_CONFIG);
635                 config.setAutoSave(false);
636 
637                 long ver = config.getLong(VERSION_ELEMENT);
638 
639                 if (ver == VERSION_TAG) {
640                     // Nothing to do
641                 } else if (isDevMode() || isDailyBuild()) {
642                     // Nothing to do
643                 } else {
644                     // Backup the old one
645                     LOG.info("Backing up config file to " + FILE_CONFIG + ".bak");
646                     f = new File(FILE_CONFIG);
647                     try {
648                         copier.copy(f, new File(FILE_CONFIG + ".bak"));
649                     } catch (IOException e) {
650                         String msg =
651                                 "Failed to backup config file "
652                                         + FILE_CONFIG
653                                         + " to "
654                                         + FILE_CONFIG
655                                         + ".bak "
656                                         + e.getMessage();
657                         System.err.println(msg);
658                         LOG.error(msg, e);
659                     }
660 
661                     if (ver == V_PAROS_TAG) {
662                         upgradeFrom1_1_0(config);
663                         upgradeFrom1_2_0(config);
664                     }
665                     if (ver <= V_1_0_0_TAG) {
666                         // Nothing to do
667                     }
668                     if (ver <= V_1_1_0_TAG) {
669                         upgradeFrom1_1_0(config);
670                     }
671                     if (ver <= V_1_2_0_TAG) {
672                         upgradeFrom1_2_0(config);
673                     }
674                     if (ver <= V_1_2_1_TAG) {
675                         // Nothing to do
676                     }
677                     if (ver <= V_1_3_0_TAG) {
678                         // Nothing to do
679                     }
680                     if (ver <= V_1_3_1_TAG) {
681                         // Nothing to do
682                     }
683                     if (ver <= V_1_4_1_TAG) {
684                         upgradeFrom1_4_1(config);
685                     }
686                     if (ver <= V_2_0_0_TAG) {
687                         upgradeFrom2_0_0(config);
688                     }
689                     if (ver <= V_2_1_0_TAG) {
690                         // Nothing to do
691                     }
692                     if (ver <= V_2_2_0_TAG) {
693                         upgradeFrom2_2_0(config);
694                     }
695                     if (ver <= V_2_2_2_TAG) {
696                         upgradeFrom2_2_2(config);
697                     }
698                     if (ver <= V_2_3_1_TAG) {
699                         upgradeFrom2_3_1(config);
700                     }
701                     if (ver <= V_2_4_3_TAG) {
702                         upgradeFrom2_4_3(config);
703                     }
704                     if (ver <= V_2_5_0_TAG) {
705                         upgradeFrom2_5_0(config);
706                     }
707                     if (ver <= V_2_7_0_TAG) {
708                         upgradeFrom2_7_0(config);
709                     }
710                     if (ver <= V_2_8_0_TAG) {
711                         upgradeFrom2_8_0(config);
712                     }
713                     if (ver <= V_2_9_0_TAG) {
714                         upgradeFrom2_9_0(config);
715                     }
716                     if (ver <= V_2_10_0_TAG) {
717                         upgradeFrom2_10_0(config);
718                     }
719 
720                     // Execute always to pick installer choices.
721                     updateCfuFromDefaultConfig(config);
722 
723                     LOG.info("Upgraded from " + ver);
724 
725                     setLatestVersion(config);
726                 }
727 
728             } catch (ConfigurationException | ConversionException | NoSuchElementException e) {
729                 handleMalformedConfigFile(e);
730             }
731         } catch (Exception e) {
732             System.err.println(
733                     "Unable to upgrade config file " + FILE_CONFIG + " " + e.getMessage());
734             e.printStackTrace(System.err);
735             System.exit(1);
736         }
737 
738         // ZAP: Init i18n
739         Locale locale = loadLocale(overrides);
740 
741         Locale.setDefault(locale);
742 
743         messages = new I18N(locale);
744     }
745 
loadLocale(ControlOverrides overrides)746     private Locale loadLocale(ControlOverrides overrides) {
747         try {
748             String lang = null;
749             if (overrides != null) {
750                 lang = overrides.getOrderedConfigs().get(OptionsParamView.LOCALE);
751             }
752             if (lang == null || lang.isEmpty()) {
753                 XMLConfiguration config = new ZapXmlConfiguration(FILE_CONFIG);
754                 config.setAutoSave(false);
755 
756                 lang = config.getString(OptionsParamView.LOCALE, OptionsParamView.DEFAULT_LOCALE);
757                 if (lang.length() == 0) {
758                     lang = OptionsParamView.DEFAULT_LOCALE;
759                 }
760             }
761 
762             String[] langArray = lang.split("_");
763             return new Locale(langArray[0], langArray[1]);
764 
765         } catch (Exception e) {
766             System.out.println("Failed to load locale " + e);
767         }
768         return Locale.ENGLISH;
769     }
770 
setUpLogging()771     private void setUpLogging() throws IOException {
772         backupLegacyLog4jConfig();
773 
774         String fileName = "log4j2.properties";
775         File logFile = new File(zapHome, fileName);
776         if (!logFile.exists()) {
777             String defaultConfig = "/org/zaproxy/zap/resources/log4j2-home.properties";
778             copyFileToHome(logFile.toPath(), "xml/" + fileName, defaultConfig);
779         }
780 
781         Configurator.reconfigure(logFile.toURI());
782     }
783 
backupLegacyLog4jConfig()784     private static void backupLegacyLog4jConfig() {
785         String fileName = "log4j.properties";
786         Path backupLegacyConfig = Paths.get(zapHome, fileName + ".bak");
787         if (Files.exists(backupLegacyConfig)) {
788             logAndPrintInfo("Ignoring legacy log4j.properties file, backup already exists.");
789             return;
790         }
791 
792         Path legacyConfig = Paths.get(zapHome, fileName);
793         if (Files.exists(legacyConfig)) {
794             logAndPrintInfo("Creating backup of legacy log4j.properties file...");
795             try {
796                 Files.move(legacyConfig, backupLegacyConfig);
797             } catch (IOException e) {
798                 logAndPrintError("Failed to backup legacy Log4j configuration file:", e);
799             }
800         }
801     }
802 
handleMalformedConfigFile(Exception e)803     private void handleMalformedConfigFile(Exception e) throws IOException {
804         logAndPrintError("Failed to load/upgrade config file:", e);
805         try {
806             Path backupPath = Paths.get(zapHome, "config-" + Math.random() + ".xml.bak");
807             logAndPrintInfo("Creating back up for user inspection: " + backupPath);
808             Files.copy(Paths.get(FILE_CONFIG), backupPath);
809             logAndPrintInfo("Back up successfully created.");
810         } catch (IOException ioe) {
811             logAndPrintError("Failed to backup file:", ioe);
812         }
813         logAndPrintInfo("Using default config file...");
814         copyDefaultConfigFile();
815     }
816 
logAndPrintError(String message, Exception e)817     private static void logAndPrintError(String message, Exception e) {
818         LOG.error(message, e);
819         System.err.println(message);
820         e.printStackTrace();
821     }
822 
logAndPrintInfo(String message)823     private static void logAndPrintInfo(String message) {
824         LOG.info(message);
825         System.out.println(message);
826     }
827 
copyProperty(XMLConfiguration fromConfig, XMLConfiguration toConfig, String key)828     private void copyProperty(XMLConfiguration fromConfig, XMLConfiguration toConfig, String key) {
829         toConfig.setProperty(key, fromConfig.getProperty(key));
830     }
831 
copyAllProperties( XMLConfiguration fromConfig, XMLConfiguration toConfig, String prefix)832     private void copyAllProperties(
833             XMLConfiguration fromConfig, XMLConfiguration toConfig, String prefix) {
834         Iterator<String> iter = fromConfig.getKeys(prefix);
835         while (iter.hasNext()) {
836             String key = iter.next();
837             copyProperty(fromConfig, toConfig, key);
838         }
839     }
840 
upgradeFrom1_1_0(XMLConfiguration config)841     private void upgradeFrom1_1_0(XMLConfiguration config) throws ConfigurationException {
842         // Upgrade the regexs
843         // ZAP: Changed to use ZapXmlConfiguration, to enforce the same character encoding when
844         // reading/writing configurations.
845         XMLConfiguration newConfig = new ZapXmlConfiguration(getUrlDefaultConfigFile());
846         newConfig.setAutoSave(false);
847 
848         copyAllProperties(newConfig, config, "pscans");
849     }
850 
upgradeFrom1_2_0(XMLConfiguration config)851     private void upgradeFrom1_2_0(XMLConfiguration config) throws ConfigurationException {
852         // Upgrade the regexs
853         // ZAP: Changed to use ZapXmlConfiguration, to enforce the same character encoding when
854         // reading/writing configurations.
855         XMLConfiguration newConfig = new ZapXmlConfiguration(getUrlDefaultConfigFile());
856         newConfig.setAutoSave(false);
857 
858         copyProperty(newConfig, config, "view.brkPanelView");
859         copyProperty(newConfig, config, "view.showMainToolbar");
860     }
861 
upgradeFrom1_4_1(XMLConfiguration config)862     private void upgradeFrom1_4_1(XMLConfiguration config) {
863         // As the POST_FORM option for the spider has been updated from int to boolean, keep
864         // compatibility for old versions
865         Object postForm = config.getProperty("spider.postform");
866         if (postForm != null) {
867             boolean enabled = !"0".equals(postForm.toString());
868             config.setProperty("spider.postform", enabled);
869             config.setProperty("spider.processform", enabled);
870         }
871 
872         // Move the old session tokens to the new "httpsessions" hierarchy and
873         // delete the old "session" hierarchy as it's no longer used/needed.
874         String[] tokens = config.getStringArray("session.tokens");
875         for (int i = 0; i < tokens.length; ++i) {
876             String elementBaseKey = "httpsessions.tokens.token(" + i + ").";
877 
878             config.setProperty(elementBaseKey + "name", tokens[i]);
879             config.setProperty(elementBaseKey + "enabled", Boolean.TRUE);
880         }
881         config.clearTree("session");
882 
883         // Update the anti CSRF tokens elements/hierarchy.
884         tokens = config.getStringArray("anticsrf.tokens");
885         config.clearTree("anticsrf.tokens");
886         for (int i = 0; i < tokens.length; ++i) {
887             String elementBaseKey = "anticsrf.tokens.token(" + i + ").";
888 
889             config.setProperty(elementBaseKey + "name", tokens[i]);
890             config.setProperty(elementBaseKey + "enabled", Boolean.TRUE);
891         }
892 
893         // Update the invoke applications elements/hierarchy.
894         List<Object[]> oldData = new ArrayList<>();
895         for (int i = 0; ; i++) {
896             String baseKey = "invoke.A" + i + ".";
897             String host = config.getString(baseKey + "name");
898             if (host == null || "".equals(host)) {
899                 break;
900             }
901 
902             Object[] data = new Object[6];
903             data[0] = host;
904             data[1] = config.getString(baseKey + "directory", "");
905             data[2] = config.getString(baseKey + "command");
906             data[3] = config.getString(baseKey + "parameters");
907             data[4] = config.getBoolean(baseKey + "output", true);
908             data[5] = config.getBoolean(baseKey + "note", false);
909             oldData.add(data);
910         }
911         config.clearTree("invoke.A");
912         for (int i = 0, size = oldData.size(); i < size; ++i) {
913             String elementBaseKey = "invoke.apps.app(" + i + ").";
914             Object[] data = oldData.get(i);
915 
916             config.setProperty(elementBaseKey + "name", data[0]);
917             config.setProperty(elementBaseKey + "directory", data[1]);
918             config.setProperty(elementBaseKey + "command", data[2]);
919             config.setProperty(elementBaseKey + "parameters", data[3]);
920             config.setProperty(elementBaseKey + "output", data[4]);
921             config.setProperty(elementBaseKey + "note", data[5]);
922             config.setProperty(elementBaseKey + "enabled", Boolean.TRUE);
923         }
924 
925         // Update the authentication elements/hierarchy.
926         oldData = new ArrayList<>();
927         for (int i = 0; ; i++) {
928             String baseKey = "connection.auth.A" + i + ".";
929             String host = config.getString(baseKey + "hostName");
930             if (host == null || "".equals(host)) {
931                 break;
932             }
933 
934             Object[] data = new Object[5];
935             data[0] = host;
936             data[1] = Integer.valueOf(config.getString(baseKey + "port", "80"));
937             data[2] = config.getString(baseKey + "userName");
938             data[3] = config.getString(baseKey + "password");
939             data[4] = config.getString(baseKey + "realm");
940             oldData.add(data);
941         }
942         config.clearTree("connection.auth.A");
943         for (int i = 0, size = oldData.size(); i < size; ++i) {
944             String elementBaseKey = "connection.auths.auth(" + i + ").";
945             Object[] data = oldData.get(i);
946 
947             config.setProperty(elementBaseKey + "name", "Auth " + i);
948             config.setProperty(elementBaseKey + "hostName", data[0]);
949             config.setProperty(elementBaseKey + "port", data[1]);
950             config.setProperty(elementBaseKey + "userName", data[2]);
951             config.setProperty(elementBaseKey + "password", data[3]);
952             config.setProperty(elementBaseKey + "realm", data[4]);
953             config.setProperty(elementBaseKey + "enabled", Boolean.TRUE);
954         }
955 
956         // Update the passive scan elements/hierarchy.
957         String[] names = config.getStringArray("pscans.names");
958         oldData = new ArrayList<>();
959         for (String pscanName : names) {
960             String baseKey = "pscans." + pscanName + ".";
961 
962             Object[] data = new Object[8];
963             data[0] = pscanName;
964             data[1] = config.getString(baseKey + "type");
965             data[2] = config.getString(baseKey + "config");
966             data[3] = config.getString(baseKey + "reqUrlRegex");
967             data[4] = config.getString(baseKey + "reqHeadRegex");
968             data[5] = config.getString(baseKey + "resHeadRegex");
969             data[6] = config.getString(baseKey + "resBodyRegex");
970             data[7] = config.getBoolean(baseKey + "enabled");
971             oldData.add(data);
972         }
973         config.clearTree("pscans.names");
974         for (String pscanName : names) {
975             config.clearTree("pscans." + pscanName);
976         }
977         for (int i = 0, size = oldData.size(); i < size; ++i) {
978             String elementBaseKey = "pscans.autoTagScanners.scanner(" + i + ").";
979             Object[] data = oldData.get(i);
980 
981             config.setProperty(elementBaseKey + "name", data[0]);
982             config.setProperty(elementBaseKey + "type", data[1]);
983             config.setProperty(elementBaseKey + "config", data[2]);
984             config.setProperty(elementBaseKey + "reqUrlRegex", data[3]);
985             config.setProperty(elementBaseKey + "reqHeadRegex", data[4]);
986             config.setProperty(elementBaseKey + "resHeadRegex", data[5]);
987             config.setProperty(elementBaseKey + "resBodyRegex", data[6]);
988             config.setProperty(elementBaseKey + "enabled", data[7]);
989         }
990     }
991 
upgradeFrom2_0_0(XMLConfiguration config)992     private void upgradeFrom2_0_0(XMLConfiguration config) {
993         String forcedBrowseFile = config.getString("bruteforce.defaultFile", "");
994         if (!"".equals(forcedBrowseFile)) {
995             String absolutePath = "";
996             // Try the 'local' dir first
997             File f = new File(DIRBUSTER_CUSTOM_DIR + File.separator + forcedBrowseFile);
998             if (!f.exists()) {
999                 f = new File(DIRBUSTER_DIR + File.separator + forcedBrowseFile);
1000             }
1001             if (f.exists()) {
1002                 absolutePath = f.getAbsolutePath();
1003             }
1004             config.setProperty("bruteforce.defaultFile", absolutePath);
1005         }
1006 
1007         // Remove the manual request editor configurations that were incorrectly created.
1008         config.clearTree("nullrequest");
1009         config.clearTree("nullresponse");
1010     }
1011 
upgradeFrom2_2_0(XMLConfiguration config)1012     private void upgradeFrom2_2_0(XMLConfiguration config) {
1013         try {
1014             if (config.getInt(OptionsParamCheckForUpdates.CHECK_ON_START, 0) == 0) {
1015                 /*
1016                  * Check-for-updates on start disabled - force another prompt to ask the user,
1017                  * as this option can have been unset incorrectly before.
1018                  * And we want to encourage users to use this ;)
1019                  */
1020                 config.setProperty(OptionsParamCheckForUpdates.DAY_LAST_CHECKED, "");
1021             }
1022         } catch (ConversionException e) {
1023             LOG.debug(
1024                     "The option " + OptionsParamCheckForUpdates.CHECK_ON_START + " is not an int.",
1025                     e);
1026         }
1027         // Clear the block list - addons were incorrectly added to this if an update failed
1028         config.setProperty(AddOnLoader.ADDONS_BLOCK_LIST, "");
1029     }
1030 
upgradeFrom2_2_2(XMLConfiguration config)1031     private void upgradeFrom2_2_2(XMLConfiguration config) {
1032         try {
1033             // Change the type of the option from int to boolean.
1034             int oldValue = config.getInt(OptionsParamCheckForUpdates.CHECK_ON_START, 1);
1035             config.setProperty(OptionsParamCheckForUpdates.CHECK_ON_START, oldValue != 0);
1036         } catch (ConversionException e) {
1037             LOG.debug(
1038                     "The option "
1039                             + OptionsParamCheckForUpdates.CHECK_ON_START
1040                             + " is no longer an int.",
1041                     e);
1042         }
1043     }
1044 
upgradeFrom2_3_1(XMLConfiguration config)1045     private void upgradeFrom2_3_1(XMLConfiguration config) {
1046         // Remove old authentication options no longer used
1047         config.clearProperty("connection.confirmRemoveAuth");
1048         config.clearTree("options.auth");
1049     }
1050 
upgradeFrom2_4_3(XMLConfiguration config)1051     private void upgradeFrom2_4_3(XMLConfiguration config) {
1052         List<Object[]> oldData = new ArrayList<>();
1053         // Convert extensions' options to not use extensions' names as XML element names
1054         for (Iterator<String> it = config.getKeys("ext"); it.hasNext(); ) {
1055             String key = it.next();
1056 
1057             Object[] data = new Object[2];
1058             data[0] = key.substring(4);
1059             data[1] = config.getBoolean(key);
1060             oldData.add(data);
1061         }
1062         config.clearTree("ext");
1063 
1064         for (int i = 0, size = oldData.size(); i < size; ++i) {
1065             String elementBaseKey = "extensions.extension(" + i + ").";
1066             Object[] data = oldData.get(i);
1067 
1068             config.setProperty(elementBaseKey + "name", data[0]);
1069             config.setProperty(elementBaseKey + "enabled", data[1]);
1070         }
1071     }
1072 
upgradeFrom2_5_0(XMLConfiguration config)1073     private static void upgradeFrom2_5_0(XMLConfiguration config) {
1074         String oldConfigKey = "proxy.modifyAcceptEncoding";
1075         config.setProperty(
1076                 "proxy.removeUnsupportedEncodings", config.getBoolean(oldConfigKey, true));
1077         config.clearProperty(oldConfigKey);
1078 
1079         // Convert passive scanners options to new structure
1080         Set<String> classnames = new HashSet<>();
1081         for (Iterator<String> it = config.getKeys(); it.hasNext(); ) {
1082             String key = it.next();
1083             if (!key.startsWith("pscans.org")) {
1084                 continue;
1085             }
1086             classnames.add(key.substring(7, key.lastIndexOf('.')));
1087         }
1088 
1089         List<Object[]> oldData = new ArrayList<>();
1090         for (String classname : classnames) {
1091             Object[] data = new Object[3];
1092             data[0] = classname;
1093             data[1] = config.getBoolean("pscans." + classname + ".enabled", true);
1094             data[2] = config.getString("pscans." + classname + ".level", "");
1095             oldData.add(data);
1096         }
1097 
1098         config.clearTree("pscans.org");
1099 
1100         for (int i = 0, size = oldData.size(); i < size; ++i) {
1101             String elementBaseKey = "pscans.pscanner(" + i + ").";
1102             Object[] data = oldData.get(i);
1103 
1104             config.setProperty(elementBaseKey + "classname", data[0]);
1105             config.setProperty(elementBaseKey + "enabled", data[1]);
1106             config.setProperty(elementBaseKey + "level", data[2]);
1107         }
1108     }
1109 
upgradeFrom2_7_0(XMLConfiguration config)1110     private static void upgradeFrom2_7_0(XMLConfiguration config) {
1111         // Remove options from SNI Terminator.
1112         config.clearTree("sniterm");
1113 
1114         String certUseKey = "certificate.use";
1115         try {
1116             // Change the type of the option from int to boolean.
1117             int oldValue = config.getInt(certUseKey, 0);
1118             config.setProperty(certUseKey, oldValue != 0);
1119         } catch (ConversionException e) {
1120             LOG.debug("The option " + certUseKey + " is no longer an int.", e);
1121         }
1122     }
1123 
upgradeFrom2_8_0(XMLConfiguration config)1124     private static void upgradeFrom2_8_0(XMLConfiguration config) {
1125         // Update to a newer default user agent
1126         config.setProperty(
1127                 ConnectionParam.DEFAULT_USER_AGENT, ConnectionParam.DEFAULT_DEFAULT_USER_AGENT);
1128         updatePscanTagMailtoPattern(config);
1129     }
1130 
upgradeFrom2_9_0(XMLConfiguration config)1131     static void upgradeFrom2_9_0(XMLConfiguration config) {
1132         String oldKeyName = ".markocurrences"; // This is the typo we want to fix
1133         String newKeyName = ".markoccurrences";
1134         config.getKeys()
1135                 .forEachRemaining(
1136                         key -> {
1137                             if (key.endsWith(oldKeyName)) {
1138                                 config.setProperty(
1139                                         key.replace(oldKeyName, newKeyName),
1140                                         config.getProperty(key));
1141                                 config.clearProperty(key);
1142                             }
1143                         });
1144         // Update to a newer default user agent
1145         config.setProperty(
1146                 ConnectionParam.DEFAULT_USER_AGENT, ConnectionParam.DEFAULT_DEFAULT_USER_AGENT);
1147         // Use new Look and Feel
1148         config.setProperty(
1149                 OptionsParamView.LOOK_AND_FEEL, OptionsParamView.DEFAULT_LOOK_AND_FEEL_NAME);
1150         config.setProperty(
1151                 OptionsParamView.LOOK_AND_FEEL_CLASS, OptionsParamView.DEFAULT_LOOK_AND_FEEL_CLASS);
1152     }
1153 
upgradeFrom2_10_0(XMLConfiguration config)1154     private static void upgradeFrom2_10_0(XMLConfiguration config) {
1155         // Update to a newer default user agent
1156         config.setProperty(
1157                 ConnectionParam.DEFAULT_USER_AGENT, ConnectionParam.DEFAULT_DEFAULT_USER_AGENT);
1158     }
1159 
updatePscanTagMailtoPattern(XMLConfiguration config)1160     private static void updatePscanTagMailtoPattern(XMLConfiguration config) {
1161         String autoTagScannersKey = "pscans.autoTagScanners.scanner";
1162         List<HierarchicalConfiguration> tagScanners = config.configurationsAt(autoTagScannersKey);
1163         String badPattern = "<.*href\\s*['\"]?mailto:";
1164         String goodPattern = "<.*href\\s*=\\s*['\"]?mailto:";
1165 
1166         for (int i = 0, size = tagScanners.size(); i < size; ++i) {
1167             String currentKeyResBodyRegex = autoTagScannersKey + "(" + i + ").resBodyRegex";
1168             if (config.getProperty(currentKeyResBodyRegex).equals(badPattern)) {
1169                 config.setProperty(currentKeyResBodyRegex, goodPattern);
1170                 break;
1171             }
1172         }
1173     }
1174 
updateCfuFromDefaultConfig(XMLConfiguration config)1175     private static void updateCfuFromDefaultConfig(XMLConfiguration config) {
1176         Path path = getPathDefaultConfigFile();
1177         if (!Files.exists(path)) {
1178             return;
1179         }
1180 
1181         ZapXmlConfiguration defaultConfig;
1182         try {
1183             defaultConfig = new ZapXmlConfiguration(path.toFile());
1184         } catch (ConfigurationException e) {
1185             logAndPrintError("Failed to read default configuration file " + path, e);
1186             return;
1187         }
1188 
1189         copyPropertyIfSet(defaultConfig, config, "start.checkForUpdates");
1190         copyPropertyIfSet(defaultConfig, config, "start.downloadNewRelease");
1191         copyPropertyIfSet(defaultConfig, config, "start.checkAddonUpdates");
1192         copyPropertyIfSet(defaultConfig, config, "start.installAddonUpdates");
1193         copyPropertyIfSet(defaultConfig, config, "start.installScannerRules");
1194         copyPropertyIfSet(defaultConfig, config, "start.reportReleaseAddons");
1195         copyPropertyIfSet(defaultConfig, config, "start.reportBetaAddons");
1196         copyPropertyIfSet(defaultConfig, config, "start.reportAlphaAddons");
1197     }
1198 
copyPropertyIfSet(XMLConfiguration from, XMLConfiguration to, String key)1199     private static void copyPropertyIfSet(XMLConfiguration from, XMLConfiguration to, String key) {
1200         Object value = from.getProperty(key);
1201         if (value != null) {
1202             to.setProperty(key, value);
1203         }
1204     }
1205 
setLocale(String loc)1206     public static void setLocale(String loc) {
1207         String[] langArray = loc.split("_");
1208         Locale locale = new Locale(langArray[0], langArray[1]);
1209 
1210         Locale.setDefault(locale);
1211         if (messages == null) {
1212             messages = new I18N(locale);
1213         } else {
1214             messages.setLocale(locale);
1215         }
1216     }
1217 
getLocale()1218     public static Locale getLocale() {
1219         return messages.getLocal();
1220     }
1221 
1222     /**
1223      * Returns the system's {@code Locale} (as determined by the JVM at startup, {@link
1224      * Locale#getDefault()}). Should be used to show locale dependent information in the system's
1225      * locale.
1226      *
1227      * <p><strong>Note:</strong> The default locale is overridden with the ZAP's user defined
1228      * locale/language.
1229      *
1230      * @return the system's {@code Locale}
1231      * @see Locale#setDefault(Locale)
1232      */
getSystemsLocale()1233     public static Locale getSystemsLocale() {
1234         return SYSTEMS_LOCALE;
1235     }
1236 
getInstance()1237     public static Constant getInstance() {
1238         if (instance == null) {
1239             // ZAP: Changed to use the method createInstance().
1240             createInstance(null);
1241         }
1242         return instance;
1243     }
1244 
createInstance(ControlOverrides overrides)1245     public static synchronized void createInstance(ControlOverrides overrides) {
1246         if (instance == null) {
1247             instance = new Constant(overrides);
1248         }
1249     }
1250 
setAcceleratorKeys()1251     private void setAcceleratorKeys() {
1252 
1253         // Undo/Redo
1254         if (Constant.isMacOsX()) {
1255             ACCELERATOR_UNDO = "meta Z";
1256             ACCELERATOR_REDO = "meta shift Z";
1257             ACCELERATOR_TRIGGER_KEY = "Meta";
1258         } else {
1259             ACCELERATOR_UNDO = "control Z";
1260             ACCELERATOR_REDO = "control Y";
1261             ACCELERATOR_TRIGGER_KEY = "Control";
1262         }
1263     }
1264 
1265     // Determine Windows Operating System
1266     // ZAP: Changed to final.
1267     private static final Pattern patternWindows =
1268             Pattern.compile("window", Pattern.CASE_INSENSITIVE);
1269 
isWindows()1270     public static boolean isWindows() {
1271         String os_name = System.getProperty("os.name");
1272 
1273         Matcher matcher = patternWindows.matcher(os_name);
1274         return matcher.find();
1275     }
1276 
1277     // Determine Linux Operating System
1278     // ZAP: Changed to final.
1279     private static final Pattern patternLinux = Pattern.compile("linux", Pattern.CASE_INSENSITIVE);
1280 
isLinux()1281     public static boolean isLinux() {
1282         String os_name = System.getProperty("os.name");
1283         Matcher matcher = patternLinux.matcher(os_name);
1284         return matcher.find();
1285     }
1286 
1287     // Determine Windows Operating System
1288     // ZAP: Changed to final.
1289     private static final Pattern patternMacOsX = Pattern.compile("mac", Pattern.CASE_INSENSITIVE);
1290 
isMacOsX()1291     public static boolean isMacOsX() {
1292         String os_name = System.getProperty("os.name");
1293         Matcher matcher = patternMacOsX.matcher(os_name);
1294         return matcher.find();
1295     }
1296 
setZapHome(String dir)1297     public static void setZapHome(String dir) {
1298         zapHome = getAbsolutePath(dir);
1299     }
1300 
1301     /**
1302      * Returns the absolute path for the given {@code directory}.
1303      *
1304      * <p>The path is terminated with a separator.
1305      *
1306      * @param directory the directory whose path will be made absolute
1307      * @return the absolute path for the given {@code directory}, terminated with a separator
1308      * @since 2.4.0
1309      */
getAbsolutePath(String directory)1310     private static String getAbsolutePath(String directory) {
1311         String realPath = Paths.get(directory).toAbsolutePath().toString();
1312         String separator = FileSystems.getDefault().getSeparator();
1313         if (!realPath.endsWith(separator)) {
1314             realPath += separator;
1315         }
1316         return realPath;
1317     }
1318 
getZapHome()1319     public static String getZapHome() {
1320         return zapHome;
1321     }
1322 
1323     /**
1324      * Returns the path to default configuration file, located in installation directory.
1325      *
1326      * <p><strong>Note:</strong> The default configuration file is not guaranteed to exist, for
1327      * example, when running just with ZAP JAR.
1328      *
1329      * @return the {@code Path} to default configuration file.
1330      * @since 2.4.2
1331      */
getPathDefaultConfigFile()1332     public static Path getPathDefaultConfigFile() {
1333         return Paths.get(getZapInstall(), "xml", FILE_CONFIG_NAME);
1334     }
1335 
getContextsDir()1336     public static File getContextsDir() {
1337         return getFromHomeDir(USER_CONTEXTS_DIR);
1338     }
1339 
getPoliciesDir()1340     public static File getPoliciesDir() {
1341         return getFromHomeDir(USER_POLICIES_DIR);
1342     }
1343 
getFromHomeDir(String subDir)1344     private static File getFromHomeDir(String subDir) {
1345         Path path = Paths.get(Constant.getZapHome(), subDir);
1346 
1347         try {
1348             if (Files.notExists(path)) {
1349                 Files.createDirectory(path);
1350             }
1351         } catch (IOException e) {
1352         }
1353 
1354         if (Files.isDirectory(path) && Files.isWritable(path)) {
1355             return path.toFile();
1356         }
1357         return Model.getSingleton().getOptionsParam().getUserDirectory();
1358     }
1359 
setZapInstall(String dir)1360     public static void setZapInstall(String dir) {
1361         zapInstall = getAbsolutePath(dir);
1362     }
1363 
getZapInstall()1364     public static String getZapInstall() {
1365         if (zapInstall == null) {
1366             String path = ".";
1367             Path localDir = Paths.get(path);
1368             if (!Files.isDirectory(localDir.resolve("db"))
1369                     || !Files.isDirectory(localDir.resolve("lang"))) {
1370                 try {
1371                     Path sourceLocation =
1372                             Paths.get(
1373                                     ZAP.class
1374                                             .getProtectionDomain()
1375                                             .getCodeSource()
1376                                             .getLocation()
1377                                             .toURI());
1378                     if (!Files.isDirectory(sourceLocation)) {
1379                         sourceLocation = sourceLocation.getParent();
1380                     }
1381                     path = sourceLocation.toString();
1382                 } catch (URISyntaxException e) {
1383                     System.err.println(
1384                             "Failed to determine the ZAP installation dir: " + e.getMessage());
1385                     path = localDir.toAbsolutePath().toString();
1386                 }
1387                 // Loggers wont have been set up yet
1388                 System.out.println("Defaulting ZAP install dir to " + path);
1389             }
1390 
1391             zapInstall = getAbsolutePath(path);
1392         }
1393         return zapInstall;
1394     }
1395 
getManifest()1396     private static Manifest getManifest() {
1397         String className = Constant.class.getSimpleName() + ".class";
1398         String classPath = Constant.class.getResource(className).toString();
1399         if (!classPath.startsWith("jar")) {
1400             // Class not from JAR
1401             return null;
1402         }
1403         String manifestPath =
1404                 classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
1405         try {
1406             return new Manifest(new URL(manifestPath).openStream());
1407         } catch (Exception e) {
1408             // Ignore
1409             return null;
1410         }
1411     }
1412 
getVersionFromManifest()1413     private static String getVersionFromManifest() {
1414         Manifest manifest = getManifest();
1415         if (manifest != null) {
1416             Attributes attr = manifest.getMainAttributes();
1417             return attr.getValue("Implementation-Version");
1418         } else {
1419             return DEV_VERSION;
1420         }
1421     }
1422 
getReleaseCreateDate()1423     public static Date getReleaseCreateDate() {
1424         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
1425         Manifest manifest = getManifest();
1426         if (manifest != null) {
1427             Attributes attr = manifest.getMainAttributes();
1428             try {
1429                 return sdf.parse(attr.getValue("Create-Date"));
1430             } catch (ParseException e) {
1431                 // Ignore - treat as undated
1432             }
1433         }
1434         return null;
1435     }
1436 
getInstallDate()1437     public static Date getInstallDate() {
1438         String className = Constant.class.getSimpleName() + ".class";
1439         String classPath = Constant.class.getResource(className).toString();
1440         if (!classPath.startsWith("jar:file:")) {
1441             // Class not from JAR
1442             return null;
1443         }
1444         classPath = classPath.substring(9);
1445         int ind = classPath.indexOf("!");
1446         if (ind > 0) {
1447             classPath = classPath.substring(0, ind);
1448         }
1449         File f = new File(classPath);
1450         if (f.exists()) {
1451             // Choose the parent directories date, in case the creation date was maintained
1452             return new Date(f.getParentFile().lastModified());
1453         }
1454         return null;
1455     }
1456 
1457     /**
1458      * Tells whether or not the "dev mode" should be enabled.
1459      *
1460      * <p>Should be used to enable development related utilities/functionalities.
1461      *
1462      * @return {@code true} if the "dev mode" should be enabled, {@code false} otherwise.
1463      * @since 2.8.0
1464      */
isDevMode()1465     public static boolean isDevMode() {
1466         return devMode || isDevBuild();
1467     }
1468 
1469     /**
1470      * Sets whether or not the "dev mode" should be enabled.
1471      *
1472      * <p><strong>Note:</strong> This method should be called only by bootstrap classes.
1473      *
1474      * @param devMode {@code true} if the "dev mode" should be enabled, {@code false} otherwise.
1475      */
setDevMode(boolean devMode)1476     public static void setDevMode(boolean devMode) {
1477         Constant.devMode = devMode;
1478     }
1479 
1480     /**
1481      * Sets whether or not ZAP should be 'silent' i.e. not make any unsolicited requests.
1482      *
1483      * <p><strong>Note:</strong> This method should be called only by bootstrap classes.
1484      *
1485      * @param silent {@code true} if ZAP should be silent, {@code false} otherwise.
1486      * @since 2.8.0
1487      */
setSilent(boolean silent)1488     public static void setSilent(boolean silent) {
1489         Constant.silent = silent;
1490     }
1491 
1492     /**
1493      * Tells whether or not ZAP is running from a dev build.
1494      *
1495      * @return {@code true} if it's a dev build, {@code false} otherwise.
1496      * @see #isDevMode()
1497      */
isDevBuild()1498     public static boolean isDevBuild() {
1499         return isDevBuild(PROGRAM_VERSION);
1500     }
1501 
isDevBuild(String version)1502     public static boolean isDevBuild(String version) {
1503         // Dev releases with be called "Dev Build" date stamped builds will be of the format
1504         // D-{yyyy}-{mm}-{dd}
1505         return DEV_VERSION.equals(version);
1506     }
1507 
isDailyBuild(String version)1508     public static boolean isDailyBuild(String version) {
1509         // Date stamped builds will be of the format D-{yyyy}-{mm}-{dd}
1510         return version.startsWith("D-");
1511     }
1512 
isDailyBuild()1513     public static boolean isDailyBuild() {
1514         return isDailyBuild(PROGRAM_VERSION);
1515     }
1516 
1517     /**
1518      * If true then ZAP should not make any unsolicited requests, e.g. check-for-updates
1519      *
1520      * @return true if ZAP should not make any unsolicited requests, e.g. check-for-updates
1521      * @since 2.8.0
1522      */
isSilent()1523     public static boolean isSilent() {
1524         return silent;
1525     }
1526 
setLowMemoryOption(boolean lowMem)1527     public static void setLowMemoryOption(boolean lowMem) {
1528         if (lowMemoryOption != null) {
1529             throw new InvalidParameterException(
1530                     "Low memory option already set to " + lowMemoryOption);
1531         }
1532         lowMemoryOption = lowMem;
1533     }
1534 
isLowMemoryOptionSet()1535     public static boolean isLowMemoryOptionSet() {
1536         return lowMemoryOption != null && lowMemoryOption;
1537     }
1538 
1539     /**
1540      * Tells whether or not ZAP is running in Kali (and it's not a daily build).
1541      *
1542      * @return {@code true} if running in Kali (and it's not daily build), {@code false} otherwise
1543      */
isKali()1544     public static boolean isKali() {
1545         if (onKali == null) {
1546             onKali = Boolean.FALSE;
1547             File osReleaseFile = new File("/etc/os-release");
1548             if (isLinux() && !isDailyBuild() && osReleaseFile.exists()) {
1549                 // Ignore the fact we're on Kali if this is a daily build - they will only have been
1550                 // installed manually
1551                 try (InputStream in = Files.newInputStream(osReleaseFile.toPath())) {
1552                     Properties osProps = new Properties();
1553                     osProps.load(in);
1554                     String osLikeValue = osProps.getProperty("ID");
1555                     if (osLikeValue != null) {
1556                         String[] oSLikes = osLikeValue.split(" ");
1557                         for (String osLike : oSLikes) {
1558                             if (osLike.equalsIgnoreCase("kali")) {
1559                                 onKali = Boolean.TRUE;
1560                                 break;
1561                             }
1562                         }
1563                     }
1564                 } catch (Exception e) {
1565                     // Ignore
1566                 }
1567             }
1568         }
1569         return onKali;
1570     }
1571 
1572     /**
1573      * Returns true if ZAP is running in a container like Docker or Flatpak
1574      *
1575      * @see #getContainerName
1576      * @since 2.11.0
1577      */
isInContainer()1578     public static boolean isInContainer() {
1579         if (inContainer == null) {
1580             // This is created by the Docker files from 2.11
1581             File containerFile = new File(ZAP_CONTAINER_FILE);
1582             File flatpakFile = new File(FLATPAK_FILE);
1583             File snapFile = new File(SNAP_FILE);
1584             if (isLinux() && containerFile.exists()) {
1585                 inContainer = true;
1586                 boolean inWebSwing = System.getenv(WEBSWING_ENVVAR) != null;
1587                 try {
1588                     containerName =
1589                             new String(
1590                                             Files.readAllBytes(containerFile.toPath()),
1591                                             StandardCharsets.UTF_8)
1592                                     .trim();
1593                     if (inWebSwing) {
1594                         // Append the webswing name so we don't loose the docker image name
1595                         containerName += "." + WEBSWING_NAME;
1596                     }
1597                 } catch (IOException e) {
1598                     // Ignore
1599                 }
1600             } else if (flatpakFile.exists()) {
1601                 inContainer = true;
1602                 containerName = FLATPAK_NAME;
1603             } else if (snapFile.exists()) {
1604                 inContainer = true;
1605                 containerName = SNAP_NAME;
1606             } else {
1607                 inContainer = false;
1608             }
1609         }
1610         return inContainer;
1611     }
1612 
1613     /**
1614      * Returns the name of the container ZAP is running in (if any) e.g. zap2docker-stable, flatpak
1615      * or null if not running in a recognised container
1616      *
1617      * @since 2.11.0
1618      */
getContainerName()1619     public static String getContainerName() {
1620         isInContainer();
1621         return containerName;
1622     }
1623 }
1624