org.parosproxy.paros.CommandLine Maven / Gradle / Ivy
Show all versions of zap Show documentation
/*
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2011/04/16 Support for running ZAP as a daemon
// ZAP: 2012/03/15 Removed unnecessary castings from methods parse, getArgument and getHelp.
// Changed to use the class StringBuilder instead of the class StringBuffer in the method
// getHelp.
// ZAP: 2012/10/15 Issue 397: Support weekly builds
// ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
// ZAP: 2013/03/20 Issue 568: Allow extensions to run from the command line
// ZAP: 2013/08/30 Issue 775: Allow host to be set via the command line
// ZAP: 2013/12/03 Issue 933: Automatically determine install dir
// ZAP: 2013/12/03 Issue 934: Handle files on the command line via extension
// ZAP: 2014/01/17 Issue 987: Allow arbitrary config file values to be set via the command line
// ZAP: 2014/05/20 Issue 1191: Cmdline session params have no effect
// ZAP: 2015/04/02 Issue 321: Support multiple databases and Issue 1582: Low memory option
// ZAP: 2015/10/06 Issue 1962: Install and update add-ons from the command line
// ZAP: 2016/08/19 Issue 2782: Support -configfile
// ZAP: 2016/09/22 JavaDoc tweaks
// ZAP: 2016/11/07 Allow to disable default standard output logging
// ZAP: 2017/03/26 Allow to obtain configs in the order specified
// ZAP: 2017/05/12 Issue 3460: Support -suppinfo
// ZAP: 2017/05/31 Handle null args and include a message in all exceptions.
// ZAP: 2017/08/31 Use helper method I18N.getString(String, Object...).
// ZAP: 2017/11/21 Validate that -cmd and -daemon are not set at the same time (they are mutually
// exclusive).
// ZAP: 2017/12/26 Remove unused command line arg SP.
// ZAP: 2018/06/29 Add command line to run ZAP in dev mode.
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
// ZAP: 2019/10/09 Issue 5619: Ensure -configfile maintains key order
// ZAP: 2020/11/26 Use Log4j 2 classes for logging.
// ZAP: 2021/05/14 Remove redundant type arguments.
// ZAP: 2022/02/09 No longer parse host/port and deprecate related code.
// ZAP: 2022/02/28 Remove code deprecated in 2.6.0
// ZAP: 2022/04/11 Remove -nouseragent option.
// ZAP: 2022/08/18 Support parameters supplied to newly installed or updated add-ons.
// ZAP: 2023/01/10 Tidy up logger.
// ZAP: 2023/03/23 Read ZAP_SILENT env var.
// ZAP: 2023/10/10 Add -sbomzip option.
// ZAP: 2024/01/13 Add -loglevel option.
package org.parosproxy.paros;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.UnaryOperator;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.extension.CommandLineArgument;
import org.parosproxy.paros.extension.CommandLineListener;
import org.zaproxy.zap.ZAP;
import org.zaproxy.zap.extension.autoupdate.ExtensionAutoUpdate;
public class CommandLine {
private static final Logger LOGGER = LogManager.getLogger(CommandLine.class);
// ZAP: Made public
public static final String SESSION = "-session";
public static final String NEW_SESSION = "-newsession";
public static final String DAEMON = "-daemon";
public static final String HELP = "-help";
public static final String HELP2 = "-h";
public static final String DIR = "-dir";
public static final String VERSION = "-version";
/**
* @deprecated (2.12.0) No longer used/needed. It will be removed in a future release.
*/
@Deprecated public static final String PORT = "-port";
/**
* @deprecated (2.12.0) No longer used/needed. It will be removed in a future release.
*/
@Deprecated public static final String HOST = "-host";
public static final String CMD = "-cmd";
public static final String INSTALL_DIR = "-installdir";
public static final String CONFIG = "-config";
public static final String CONFIG_FILE = "-configfile";
public static final String LOG_LEVEL = "-loglevel";
public static final String LOWMEM = "-lowmem";
public static final String EXPERIMENTALDB = "-experimentaldb";
public static final String SUPPORT_INFO = "-suppinfo";
public static final String SBOM_ZIP = "-sbomzip";
public static final String SILENT = "-silent";
static final String SILENT_ENV_VAR = "ZAP_SILENT";
/**
* Command line option to disable the default logging through standard output.
*
* @see #isNoStdOutLog()
* @since 2.6.0
*/
public static final String NOSTDOUT = "-nostdout";
/**
* Command line option to enable "dev mode".
*
* With this option development related utilities/functionalities are enabled. For example,
* it's shown an error counter in the footer tool bar and license is implicitly accepted (thus
* not requiring to show/accept the license each time a new home is used).
*
*
Note: this mode is always enabled when running ZAP directly from source
* (i.e. not packaged in a JAR) or using a dev build.
*
* @see #isDevMode()
* @since 2.8.0
*/
public static final String DEV_MODE = "-dev";
private boolean GUI = true;
private boolean daemon = false;
private boolean reportVersion = false;
private boolean displaySupportInfo = false;
private boolean lowMem = false;
private boolean experimentalDb = false;
private boolean silent = false;
private File saveSbomZip;
private String[] args;
private String[] argsBackup;
private final Map configs = new LinkedHashMap<>();
private final Hashtable keywords = new Hashtable<>();
private List commandList = null;
/**
* Flag that indicates whether or not the default logging through standard output should be
* disabled.
*/
private boolean noStdOutLog;
/** Flag that indicates whether or not the "dev mode" is enabled. */
private boolean devMode;
private Level logLevel;
public CommandLine(String[] args) throws Exception {
this(args, System::getenv);
}
CommandLine(String[] args, UnaryOperator env) throws Exception {
this.args = args == null ? new String[0] : args;
this.argsBackup = new String[this.args.length];
System.arraycopy(this.args, 0, argsBackup, 0, this.args.length);
parseFirst(this.args);
readEnv(env);
if (isEnabled(CommandLine.CMD) && isEnabled(CommandLine.DAEMON)) {
throw new IllegalArgumentException(
"Command line arguments "
+ CommandLine.CMD
+ " and "
+ CommandLine.DAEMON
+ " cannot be used at the same time.");
}
}
private void readEnv(UnaryOperator env) {
if (env.apply(SILENT_ENV_VAR) != null) {
setSilent();
}
}
private void setSilent() {
silent = true;
Constant.setSilent(true);
}
private boolean checkPair(String[] args, String paramName, int i) throws Exception {
String key = args[i];
String value = null;
if (key == null) {
return false;
}
if (key.equalsIgnoreCase(paramName)) {
value = args[i + 1];
if (value == null) {
throw new Exception("Missing parameter for keyword '" + paramName + "'.");
}
keywords.put(paramName, value);
args[i] = null;
args[i + 1] = null;
return true;
}
return false;
}
private boolean checkSwitch(String[] args, String paramName, int i) throws Exception {
String key = args[i];
if (key == null) {
return false;
}
if (key.equalsIgnoreCase(paramName)) {
keywords.put(paramName, "");
args[i] = null;
return true;
}
return false;
}
private void parseFirst(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
if (parseSwitchs(args, i)) {
continue;
}
if (parseKeywords(args, i)) {
continue;
}
}
}
public void parse(
List commandList, Map extMap)
throws Exception {
this.parse(commandList, extMap, true);
}
/**
* Parse the command line arguments
*
* @param commandList the list of commands
* @param extMap a map of the extensions which support command line args
* @param reportUnsupported if true will report unsupported args
* @throws Exception
* @since 2.12.0
*/
public void parse(
List commandList,
Map extMap,
boolean reportUnsupported)
throws Exception {
this.commandList = commandList;
CommandLineArgument lastArg = null;
boolean found = false;
int remainingValueCount = 0;
boolean installingAddons = false;
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
continue;
}
found = false;
for (int j = 0; j < commandList.size() && !found; j++) {
CommandLineArgument[] extArg = commandList.get(j);
for (int k = 0; k < extArg.length && !found; k++) {
if (args[i].compareToIgnoreCase(extArg[k].getName()) == 0) {
// check if previous keyword satisfied its required no. of parameters
if (remainingValueCount > 0) {
throw new Exception(
"Missing parameters for keyword '" + lastArg.getName() + "'.");
}
// process this keyword
lastArg = extArg[k];
lastArg.setEnabled(true);
found = true;
if (ExtensionAutoUpdate.ADDON_INSTALL.equals(args[i])
|| ExtensionAutoUpdate.ADDON_INSTALL_ALL.equals(args[i])) {
installingAddons = true;
}
args[i] = null;
remainingValueCount = lastArg.getNumOfArguments();
}
}
}
// check if current string is a keyword preceded by '-'
if (args[i] != null && args[i].startsWith("-")) {
continue;
}
// check if there is no more expected param value
if (lastArg != null && remainingValueCount == 0) {
continue;
}
// check if consume remaining for last matched keywords
if (!found && lastArg != null) {
if (lastArg.getPattern() == null || lastArg.getPattern().matcher(args[i]).find()) {
lastArg.getArguments().add(args[i]);
if (remainingValueCount > 0) {
remainingValueCount--;
}
args[i] = null;
} else {
throw new Exception(lastArg.getErrorMessage());
}
}
}
// check if the last keyword satisfied its no. of parameters.
if (lastArg != null && remainingValueCount > 0) {
throw new Exception("Missing parameters for keyword '" + lastArg.getName() + "'.");
}
// check for supported extensions
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
continue;
}
int dotIndex = args[i].lastIndexOf(".");
if (dotIndex < 0) {
// Only support files with extensions
continue;
}
File file = new File(args[i]);
if (!file.exists() || !file.canRead()) {
// Not there or cant read .. move on
continue;
}
String ext = args[i].substring(dotIndex + 1);
CommandLineListener cll = extMap.get(ext);
if (cll != null) {
if (cll.handleFile(file)) {
found = true;
args[i] = null;
}
}
}
if (reportUnsupported && !installingAddons) {
// check if there is some unknown keywords or parameters
for (String arg : args) {
if (arg != null) {
if (arg.startsWith("-")) {
throw new Exception(
Constant.messages.getString("start.cmdline.badparam", arg));
} else {
// Assume they were trying to specify a file
File f = new File(arg);
if (!f.exists()) {
throw new Exception(
Constant.messages.getString("start.cmdline.nofile", arg));
} else if (!f.canRead()) {
throw new Exception(
Constant.messages.getString("start.cmdline.noread", arg));
} else {
// We probably dont handle this sort of file
throw new Exception(
Constant.messages.getString("start.cmdline.badfile", arg));
}
}
}
}
}
}
private boolean parseSwitchs(String[] args, int i) throws Exception {
boolean result = false;
if (checkSwitch(args, CMD, i)) {
setDaemon(false);
setGUI(false);
} else if (checkSwitch(args, DAEMON, i)) {
setDaemon(true);
setGUI(false);
} else if (checkSwitch(args, LOWMEM, i)) {
setLowMem(true);
} else if (checkSwitch(args, EXPERIMENTALDB, i)) {
setExperimentalDb(true);
} else if (checkSwitch(args, HELP, i)) {
result = true;
setGUI(false);
} else if (checkSwitch(args, HELP2, i)) {
result = true;
setGUI(false);
} else if (checkSwitch(args, VERSION, i)) {
reportVersion = true;
setDaemon(false);
setGUI(false);
} else if (checkSwitch(args, NOSTDOUT, i)) {
noStdOutLog = true;
} else if (checkSwitch(args, SUPPORT_INFO, i)) {
displaySupportInfo = true;
setDaemon(false);
setGUI(false);
} else if (checkSwitch(args, DEV_MODE, i)) {
devMode = true;
Constant.setDevMode(true);
} else if (checkSwitch(args, SILENT, i)) {
setSilent();
}
return result;
}
private boolean parseKeywords(String[] args, int i) throws Exception {
boolean result = false;
if (checkPair(args, NEW_SESSION, i)) {
result = true;
} else if (checkPair(args, SESSION, i)) {
result = true;
} else if (checkPair(args, DIR, i)) {
Constant.setZapHome(keywords.get(DIR));
result = true;
} else if (checkPair(args, LOG_LEVEL, i)) {
logLevel = Level.toLevel(keywords.get(LOG_LEVEL), null);
if (logLevel == null) {
throw new Exception("Invalid log level: \"" + keywords.get(LOG_LEVEL) + "\"");
}
result = true;
} else if (checkPair(args, INSTALL_DIR, i)) {
Constant.setZapInstall(keywords.get(INSTALL_DIR));
result = true;
} else if (checkPair(args, SBOM_ZIP, i)) {
String zipName = keywords.get(SBOM_ZIP);
this.saveSbomZip = new File(zipName);
setDaemon(false);
setGUI(false);
result = true;
} else if (checkPair(args, CONFIG, i)) {
String pair = keywords.get(CONFIG);
if (pair != null && pair.indexOf("=") > 0) {
int eqIndex = pair.indexOf("=");
this.configs.put(pair.substring(0, eqIndex), pair.substring(eqIndex + 1));
result = true;
}
} else if (checkPair(args, CONFIG_FILE, i)) {
String conf = keywords.get(CONFIG_FILE);
File confFile = new File(conf);
if (!confFile.isFile()) {
// We cant use i18n here as the messages wont have been loaded
throw new Exception("No such file: " + confFile.getAbsolutePath());
} else if (!confFile.canRead()) {
// We cant use i18n here as the messages wont have been loaded
throw new Exception("File not readable: " + confFile.getAbsolutePath());
}
Properties prop =
new Properties() {
// Override methods to ensure keys returned in order
List