diff --git a/addOns/params/CHANGELOG.md b/addOns/params/CHANGELOG.md
new file mode 100644
index 00000000000..136be566811
--- /dev/null
+++ b/addOns/params/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog
+All notable changes to this add-on will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+### Changed
+
+- Migrated from core.
+
+[0.1.0]: https://github.com/zaproxy/zap-extensions/releases/params-v0.0.1
diff --git a/addOns/params/gradle.properties b/addOns/params/gradle.properties
new file mode 100644
index 00000000000..12f33ef6eb1
--- /dev/null
+++ b/addOns/params/gradle.properties
@@ -0,0 +1,2 @@
+version=0.0.1
+release=false
diff --git a/addOns/params/params.gradle.kts b/addOns/params/params.gradle.kts
new file mode 100644
index 00000000000..1a312172332
--- /dev/null
+++ b/addOns/params/params.gradle.kts
@@ -0,0 +1,40 @@
+import org.zaproxy.gradle.addon.AddOnStatus
+
+description = "Tracks parameters, cookies, and header values on a site by site basis."
+
+zapAddOn {
+ addOnName.set("Params")
+ addOnStatus.set(AddOnStatus.RELEASE)
+
+ manifest {
+ author.set("ZAP Dev Team")
+ url.set("https://www.zaproxy.org/docs/desktop/addons/params/")
+
+ dependencies {
+ addOns {
+ register("pscan") {
+ version.set(">= 0.1.0 & < 1.0.0")
+ }
+ }
+ }
+ }
+
+ apiClientGen {
+ api.set("org.zaproxy.addon.params.ParamsAPI")
+ }
+}
+
+crowdin {
+ configuration {
+ val path = "org/zaproxy/addon/params/resources/"
+ tokens.put("%messagesPath%", path)
+ tokens.put("%helpPath%", path)
+ }
+}
+
+dependencies {
+ zapAddOn("pscan")
+
+ testImplementation(project(":testutils"))
+ testImplementation(libs.log4j.core)
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/ExtensionParams2.java b/addOns/params/src/main/java/org/zaproxy/addon/params/ExtensionParams2.java
new file mode 100644
index 00000000000..644dd069154
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/ExtensionParams2.java
@@ -0,0 +1,685 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.EventQueue;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import javax.swing.tree.TreeNode;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.control.Control;
+import org.parosproxy.paros.control.Control.Mode;
+import org.parosproxy.paros.core.scanner.NameValuePair;
+import org.parosproxy.paros.core.scanner.VariantMultipartFormParameters;
+import org.parosproxy.paros.db.DatabaseException;
+import org.parosproxy.paros.db.RecordParam;
+import org.parosproxy.paros.extension.ExtensionAdaptor;
+import org.parosproxy.paros.extension.ExtensionHook;
+import org.parosproxy.paros.extension.ExtensionHookView;
+import org.parosproxy.paros.extension.ExtensionLoader;
+import org.parosproxy.paros.extension.SessionChangedListener;
+import org.parosproxy.paros.model.Model;
+import org.parosproxy.paros.model.Session;
+import org.parosproxy.paros.model.SiteNode;
+import org.parosproxy.paros.network.HtmlParameter;
+import org.parosproxy.paros.network.HttpHeader;
+import org.parosproxy.paros.network.HttpHeaderField;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.addon.pscan.ExtensionPassiveScan2;
+import org.zaproxy.zap.extension.anticsrf.ExtensionAntiCSRF;
+import org.zaproxy.zap.extension.help.ExtensionHelp;
+import org.zaproxy.zap.extension.httpsessions.ExtensionHttpSessions;
+import org.zaproxy.zap.extension.search.ExtensionSearch;
+import org.zaproxy.zap.utils.ThreadUtils;
+import org.zaproxy.zap.view.SiteMapListener;
+import org.zaproxy.zap.view.SiteMapTreeCellRenderer;
+
+public class ExtensionParams2 extends ExtensionAdaptor implements SiteMapListener {
+
+ public static final String NAME = "ExtensionParams2";
+
+ private ParamsPanel paramsPanel = null;
+ private PopupMenuParamSearch popupMenuSearch = null;
+ private PopupMenuAddAntiCSRF popupMenuAddAntiCsrf = null;
+ private PopupMenuRemoveAntiCSRF popupMenuRemoveAntiCsrf = null;
+ private PopupMenuAddSession popupMenuAddSession = null;
+ private PopupMenuRemoveSession popupMenuRemoveSession = null;
+ private Map siteParamsMap = new HashMap<>();
+
+ private static final Logger LOGGER = LogManager.getLogger(ExtensionParams2.class);
+
+ private ExtensionHttpSessions extensionHttpSessions;
+ private ParamScanner paramScanner;
+
+ private boolean warnDbFull = true;
+
+ public ExtensionParams2() {
+ super(NAME);
+ this.setOrder(59);
+ }
+
+ // @Override
+ // public boolean isEnabled() {
+ // return false;
+ // }
+
+ @Override
+ public boolean supportsDb(String type) {
+ return true;
+ }
+
+ @Override
+ public String getUIName() {
+ return Constant.messages.getString("params.name");
+ }
+
+ @Override
+ public void hook(ExtensionHook extensionHook) {
+ super.hook(extensionHook);
+ extensionHook.addApiImplementor(new ParamsAPI(this));
+ extensionHook.addSessionListener(new SessionChangedListenerImpl());
+ extensionHook.addSiteMapListener(this);
+
+ if (getView() != null) {
+ @SuppressWarnings("unused")
+ ExtensionHookView pv = extensionHook.getHookView();
+ extensionHook.getHookView().addStatusPanel(getParamsPanel());
+
+ final ExtensionLoader extLoader = Control.getSingleton().getExtensionLoader();
+ if (extLoader.isExtensionEnabled(ExtensionSearch.NAME)) {
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuParamSearch());
+ }
+
+ if (extLoader.isExtensionEnabled(ExtensionAntiCSRF.NAME)) {
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuAddAntiCSRF());
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuRemoveAntiCSRF());
+ }
+
+ if (extLoader.isExtensionEnabled(ExtensionHttpSessions.NAME)) {
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuAddSession());
+ extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuRemoveSession());
+ }
+
+ ExtensionHelp.enableHelpKey(getParamsPanel(), "ui.tabs.params");
+ }
+
+ ExtensionPassiveScan2 extensionPassiveScan =
+ Control.getSingleton()
+ .getExtensionLoader()
+ .getExtension(ExtensionPassiveScan2.class);
+ if (extensionPassiveScan != null) {
+ paramScanner = new ParamScanner(this);
+ extensionPassiveScan.getPassiveScannersManager().add(paramScanner);
+ }
+ }
+
+ @Override
+ public void unload() {
+ super.unload();
+
+ ExtensionPassiveScan2 extensionPassiveScan =
+ Control.getSingleton()
+ .getExtensionLoader()
+ .getExtension(ExtensionPassiveScan2.class);
+ if (extensionPassiveScan != null) {
+ extensionPassiveScan.getPassiveScannersManager().remove(paramScanner);
+ }
+ }
+
+ private PopupMenuParamSearch getPopupMenuParamSearch() {
+ if (popupMenuSearch == null) {
+ popupMenuSearch = new PopupMenuParamSearch();
+ popupMenuSearch.setExtension(this);
+ }
+ return popupMenuSearch;
+ }
+
+ private PopupMenuAddAntiCSRF getPopupMenuAddAntiCSRF() {
+ if (popupMenuAddAntiCsrf == null) {
+ popupMenuAddAntiCsrf = new PopupMenuAddAntiCSRF();
+ popupMenuAddAntiCsrf.setExtension(this);
+ }
+ return popupMenuAddAntiCsrf;
+ }
+
+ private PopupMenuRemoveAntiCSRF getPopupMenuRemoveAntiCSRF() {
+ if (popupMenuRemoveAntiCsrf == null) {
+ popupMenuRemoveAntiCsrf = new PopupMenuRemoveAntiCSRF();
+ popupMenuRemoveAntiCsrf.setExtension(this);
+ }
+ return popupMenuRemoveAntiCsrf;
+ }
+
+ private PopupMenuAddSession getPopupMenuAddSession() {
+ if (popupMenuAddSession == null) {
+ popupMenuAddSession = new PopupMenuAddSession();
+ popupMenuAddSession.setExtension(this);
+ }
+ return popupMenuAddSession;
+ }
+
+ private PopupMenuRemoveSession getPopupMenuRemoveSession() {
+ if (popupMenuRemoveSession == null) {
+ popupMenuRemoveSession = new PopupMenuRemoveSession();
+ popupMenuRemoveSession.setExtension(this);
+ }
+ return popupMenuRemoveSession;
+ }
+
+ protected ParamsPanel getParamsPanel() {
+ if (paramsPanel == null) {
+ paramsPanel = new ParamsPanel(this);
+ }
+ return paramsPanel;
+ }
+
+ /**
+ * Gets the ExtensionHttpSessions, if it's enabled
+ *
+ * @return the Http Sessions extension or null, if it's not available
+ */
+ protected ExtensionHttpSessions getExtensionHttpSessions() {
+ if (extensionHttpSessions == null) {
+ extensionHttpSessions =
+ Control.getSingleton()
+ .getExtensionLoader()
+ .getExtension(ExtensionHttpSessions.class);
+ }
+ return extensionHttpSessions;
+ }
+
+ public boolean onHttpRequestSend(HttpMessage msg) {
+
+ // Check we know the site
+ String site =
+ msg.getRequestHeader().getHostName() + ":" + msg.getRequestHeader().getHostPort();
+
+ if (getView() != null) {
+ this.getParamsPanel().addSite(site);
+ }
+
+ SiteParameters sps = this.siteParamsMap.get(site);
+ if (sps == null) {
+ sps = new SiteParameters(this, site);
+ this.siteParamsMap.put(site, sps);
+ }
+
+ // Cookie Parameters
+ TreeSet params;
+ Iterator iter;
+ try {
+ params = msg.getRequestHeader().getCookieParams();
+ iter = params.iterator();
+ while (iter.hasNext()) {
+ persist(sps.addParam(site, iter.next(), msg));
+ }
+ } catch (IllegalArgumentException e) {
+ LOGGER.warn("Failed to obtain the cookies: {}", e.getMessage(), e);
+ }
+
+ // URL Parameters
+ params = msg.getUrlParams();
+ iter = params.iterator();
+ while (iter.hasNext()) {
+ persist(sps.addParam(site, iter.next(), msg));
+ }
+
+ // Form Parameters
+ // TODO flag anti csrf url ones too?
+
+ ExtensionAntiCSRF extAntiCSRF =
+ Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.class);
+
+ params = msg.getFormParams();
+ iter = params.iterator();
+ HtmlParameter param;
+ while (iter.hasNext()) {
+ param = iter.next();
+ if (extAntiCSRF != null && extAntiCSRF.isAntiCsrfToken(param.getName())) {
+ param.addFlag(HtmlParameter.Flags.anticsrf.name());
+ }
+ persist(sps.addParam(site, param, msg));
+ }
+
+ VariantMultipartFormParameters params2 = new VariantMultipartFormParameters();
+ params2.setMessage(msg);
+ for (NameValuePair nvp : params2.getParamList()) {
+ if (nvp.getType() == NameValuePair.TYPE_MULTIPART_DATA_PARAM
+ || nvp.getType() == NameValuePair.TYPE_MULTIPART_DATA_FILE_NAME) {
+ persist(
+ sps.addParam(
+ site,
+ new HtmlParameter(
+ HtmlParameter.Type.multipart,
+ nvp.getName(),
+ nvp.getValue()),
+ msg));
+ }
+ }
+
+ return true;
+ }
+
+ private String setToString(Set set) {
+ StringBuilder sb = new StringBuilder();
+ if (set == null) {
+ return "";
+ }
+ // Despite the SonarLint warning we do need to sync on the set
+ synchronized (set) {
+ for (String str : set) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ // Escape all commas in the values
+ sb.append(str.replace(",", "%2C"));
+ }
+ }
+ return sb.toString();
+ }
+
+ private void persist(HtmlParameterStats param) {
+ try {
+ if (param.getId() < 0) {
+ // Its a new one
+ RecordParam rp =
+ Model.getSingleton()
+ .getDb()
+ .getTableParam()
+ .insert(
+ param.getSite(),
+ param.getType().name(),
+ param.getName(),
+ param.getTimesUsed(),
+ setToString(param.getFlags()),
+ setToString(param.getValues()));
+ param.setId(rp.getParamId());
+ } else {
+ // Its an existing one
+ Model.getSingleton()
+ .getDb()
+ .getTableParam()
+ .update(
+ param.getId(),
+ param.getTimesUsed(),
+ setToString(param.getFlags()),
+ setToString(param.getValues()));
+ }
+ } catch (DatabaseException e) {
+ if (hasCause(e, "truncation")) {
+ LOGGER.warn("Could not add or update param: {}", param.getName());
+ LOGGER.warn(
+ "It is likely that the length of one of the data elements exceeded the column size.");
+ LOGGER.warn(e.getMessage());
+ LOGGER.debug(e.getMessage(), e);
+ } else if (hasCause(e, "Data File size limit is reached")) {
+ if (warnDbFull) {
+ warnDbFull = false;
+ LOGGER.warn("Unable to persist parameter, database is full.", e);
+ }
+ } else {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ private static boolean hasCause(Exception e, String wantedMessage) {
+ Throwable cause = e.getCause();
+ if (cause == null) {
+ return false;
+ }
+ String message = cause.getMessage();
+ if (message == null) {
+ return false;
+ }
+ return message.contains(wantedMessage);
+ }
+
+ public boolean onHttpResponseReceive(HttpMessage msg) {
+
+ // Check we know the site
+ String site =
+ msg.getRequestHeader().getHostName() + ":" + msg.getRequestHeader().getHostPort();
+
+ if (getView() != null) {
+ this.getParamsPanel().addSite(site);
+ }
+
+ SiteParameters sps = this.getSiteParameters(site);
+
+ // Cookie Parameters
+ try {
+ TreeSet params = msg.getResponseHeader().getCookieParams();
+ Iterator iter = params.iterator();
+ while (iter.hasNext()) {
+ persist(sps.addParam(site, iter.next(), msg));
+ }
+ } catch (IllegalArgumentException e) {
+ LOGGER.warn("Failed to obtain the cookies: {}", e.getMessage(), e);
+ }
+
+ // Header "Parameters"
+ List headersList = msg.getResponseHeader().getHeaders();
+ List setCookieHeaders =
+ Arrays.asList(
+ HttpHeader.SET_COOKIE.toLowerCase(), HttpHeader.SET_COOKIE2.toLowerCase());
+ for (HttpHeaderField hdrField : headersList) {
+ if (setCookieHeaders.contains(hdrField.getName().toLowerCase())) {
+ continue;
+ }
+ HtmlParameter headerParam =
+ new HtmlParameter(
+ HtmlParameter.Type.header, hdrField.getName(), hdrField.getValue());
+ ThreadUtils.invokeLater(() -> persist(sps.addParam(site, headerParam, msg)));
+ }
+
+ // TODO Only do if response URL different to request?
+ // URL Parameters
+ /*
+ params = msg.getUrlParams();
+ iter = params.iterator();
+ while (iter.hasNext()) {
+ sps.addParam(iter.next());
+ }
+ */
+
+ return true;
+ }
+
+ @Override
+ public void nodeSelected(SiteNode node) {
+ // Event from SiteMapListenner
+ this.getParamsPanel().nodeSelected(node);
+ }
+
+ @Override
+ public void onReturnNodeRendererComponent(
+ SiteMapTreeCellRenderer component, boolean leaf, SiteNode value) {}
+
+ protected void searchForSelectedParam() {
+
+ HtmlParameterStats item = this.getParamsPanel().getSelectedParam();
+ if (item != null) {
+ ExtensionSearch extSearch =
+ Control.getSingleton().getExtensionLoader().getExtension(ExtensionSearch.class);
+
+ if (extSearch != null) {
+ if (HtmlParameter.Type.url.equals(item.getType())) {
+ extSearch.search(
+ "[?&]" + Pattern.quote(item.getName()) + "=.*",
+ ExtensionSearch.Type.URL,
+ true,
+ false);
+ } else if (HtmlParameter.Type.cookie.equals(item.getType())) {
+ extSearch.search(
+ Pattern.quote(item.getName()) + "=.*",
+ ExtensionSearch.Type.Header,
+ true,
+ false);
+ } else if (HtmlParameter.Type.header.equals(item.getType())) {
+ extSearch.search(
+ Pattern.quote(item.getName()) + ":.*",
+ ExtensionSearch.Type.Header,
+ true,
+ false);
+ } else if (HtmlParameter.Type.multipart.equals(item.getType())) {
+ extSearch.search(
+ "(?i)\\s*content-disposition\\s*:.*\\s+name\\s*\\=?\\s*\\\"?"
+ + Pattern.quote(item.getName()),
+ ExtensionSearch.Type.Request,
+ true,
+ false);
+ } else {
+ // FORM
+ extSearch.search(
+ Pattern.quote(item.getName()) + "=.*",
+ ExtensionSearch.Type.Request,
+ true,
+ false);
+ }
+ }
+ }
+ }
+
+ public void addAntiCsrfToken() {
+ HtmlParameterStats item = this.getParamsPanel().getSelectedParam();
+
+ ExtensionAntiCSRF extAntiCSRF =
+ Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.class);
+
+ if (extAntiCSRF != null && item != null) {
+ extAntiCSRF.addAntiCsrfTokenName(item.getName());
+ item.addFlag(HtmlParameter.Flags.anticsrf.name());
+ // Repaint so change shows up
+ this.getParamsPanel().getParamsTable().repaint();
+
+ // Dont think we need to do this... at least until rescan option implemented ...
+ // Control.getSingleton().getMenuToolsControl().options(Constant.messages.getString("options.acsrf.title"));
+
+ }
+ }
+
+ public void removeAntiCsrfToken() {
+ HtmlParameterStats item = this.getParamsPanel().getSelectedParam();
+
+ ExtensionAntiCSRF extAntiCSRF =
+ Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.class);
+
+ if (extAntiCSRF != null && item != null) {
+ extAntiCSRF.removeAntiCsrfTokenName(item.getName());
+ item.removeFlag(HtmlParameter.Flags.anticsrf.name());
+ // Repaint so change shows up
+ this.getParamsPanel().getParamsTable().repaint();
+
+ // Dont think we need to do this... at least until rescan option implemented ...
+ // Control.getSingleton().getMenuToolsControl().options(Constant.messages.getString("options.acsrf.title"));
+ }
+ }
+
+ /**
+ * Tells whether or not the given {@code site} was already seen.
+ *
+ * @param site the site that will be checked
+ * @return {@code true} if the given {@code site} was already seen, {@code false} otherwise.
+ * @since 2.5.0
+ * @see #hasParameters(String)
+ */
+ public boolean hasSite(String site) {
+ return siteParamsMap.containsKey(site);
+ }
+
+ /**
+ * Tells whether or not the given {@code site} has parameters.
+ *
+ * @param site the site that will be checked
+ * @return {@code true} if the given {@code site} has parameters, {@code false} if not, or was
+ * not yet seen.
+ * @since 2.5.0
+ * @see #hasSite(String)
+ */
+ public boolean hasParameters(String site) {
+ SiteParameters siteParameters = siteParamsMap.get(site);
+ if (siteParameters == null) {
+ return false;
+ }
+ return siteParameters.hasParams();
+ }
+
+ public SiteParameters getSiteParameters(String site) {
+ SiteParameters sps = this.siteParamsMap.get(site);
+ if (sps == null) {
+ sps = new SiteParameters(this, site);
+ siteParamsMap.put(site, sps);
+ }
+ return sps;
+ }
+
+ public Collection getAllSiteParameters() {
+ return this.siteParamsMap.values();
+ }
+
+ /**
+ * Adds a new session token from the selected parameter. Also notifies the {@link
+ * ExtensionHttpSessions} if it's active.
+ */
+ public void addSessionToken() {
+ // Get the selected parameter
+ HtmlParameterStats item = this.getParamsPanel().getSelectedParam();
+ if (item != null) {
+
+ // If the HttpSessions extension is active, notify it of the new session token
+ ExtensionHttpSessions extSession = this.getExtensionHttpSessions();
+ if (extSession != null) {
+ extSession.addHttpSessionToken(
+ this.getParamsPanel().getCurrentSite(), item.getName());
+ }
+
+ // Flag the item accordingly
+ item.addFlag(HtmlParameter.Flags.session.name());
+ // Repaint so change shows up
+ this.getParamsPanel().getParamsTable().repaint();
+ }
+ }
+
+ /**
+ * Removes the currently selected parameter as a session token. Also notifies the {@link
+ * ExtensionHttpSessions} if it's active.
+ */
+ public void removeSessionToken() {
+ HtmlParameterStats item = this.getParamsPanel().getSelectedParam();
+
+ if (item != null) {
+ // If the HttpSessions extension is active, notify it of the removed session token
+ ExtensionHttpSessions extSession = this.getExtensionHttpSessions();
+ if (extSession != null) {
+ extSession.removeHttpSessionToken(
+ this.getParamsPanel().getCurrentSite(), item.getName());
+ }
+
+ // Unflag the item accordingly
+ item.removeFlag(HtmlParameter.Flags.session.name());
+ // Repaint so change shows up
+ this.getParamsPanel().getParamsTable().repaint();
+ }
+ }
+
+ public HtmlParameterStats getSelectedParam() {
+ return this.getParamsPanel().getSelectedParam();
+ }
+
+ @Override
+ public String getAuthor() {
+ return Constant.ZAP_TEAM;
+ }
+
+ @Override
+ public String getDescription() {
+ return Constant.messages.getString("params.desc");
+ }
+
+ private class SessionChangedListenerImpl implements SessionChangedListener {
+
+ @Override
+ public void sessionAboutToChange(Session session) {
+ if (hasView()) {
+ getParamsPanel().reset();
+ }
+ }
+
+ @Override
+ public void sessionChanged(final Session session) {
+ warnDbFull = true;
+
+ if (EventQueue.isDispatchThread()) {
+ sessionChangedEventHandler(session);
+
+ } else {
+ try {
+ EventQueue.invokeAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ sessionChangedEventHandler(session);
+ }
+ });
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+ if (hasView()) {
+ ThreadUtils.invokeAndWaitHandled(getParamsPanel()::reset);
+ }
+ }
+
+ private void sessionChangedEventHandler(Session session) {
+ // Clear all scans
+ siteParamsMap = new HashMap<>();
+ if (getView() != null) {
+ getParamsPanel().reset();
+ }
+ if (session == null) {
+ // Closedown
+ return;
+ }
+
+ // Repopulate
+ SiteNode root = session.getSiteTree().getRoot();
+ @SuppressWarnings("unchecked")
+ Enumeration en = root.children();
+ while (en.hasMoreElements()) {
+ String site = ((SiteNode) en.nextElement()).getNodeName();
+ if (getView() != null) {
+ getParamsPanel().addSite(site);
+ }
+ }
+
+ try {
+ List params = Model.getSingleton().getDb().getTableParam().getAll();
+
+ for (RecordParam param : params) {
+ SiteParameters sps = getSiteParameters(param.getSite());
+ sps.addParam(param.getSite(), param);
+ }
+ } catch (DatabaseException e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void sessionScopeChanged(Session session) {
+ // Nothing to do
+ }
+
+ @Override
+ public void sessionModeChanged(Mode mode) {
+ // Nothing to do
+ }
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/HtmlParameterStats.java b/addOns/params/src/main/java/org/zaproxy/addon/params/HtmlParameterStats.java
new file mode 100644
index 00000000000..93b951ef705
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/HtmlParameterStats.java
@@ -0,0 +1,163 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.parosproxy.paros.network.HtmlParameter;
+
+public class HtmlParameterStats implements Comparable {
+ private long id = -1;
+ private String site;
+ private String name;
+ private HtmlParameter.Type type;
+ private int timesUsed = 0;
+ private Set flags = Collections.synchronizedSet(new HashSet<>());
+ private Set values = Collections.synchronizedSet(new HashSet<>());
+
+ public HtmlParameterStats(
+ String site, String name, HtmlParameter.Type type, String value, Set flags) {
+ this.site = site;
+ this.name = name;
+ this.type = type;
+ this.addValue(value);
+ this.flags = flags;
+ this.incTimesUsed();
+ }
+
+ public HtmlParameterStats(
+ long id,
+ String site,
+ String name,
+ String type,
+ int timesUsed,
+ Set values,
+ Set flags) {
+ this.id = id;
+ this.site = site;
+ this.name = name;
+ this.type = HtmlParameter.Type.valueOf(type);
+ this.timesUsed = timesUsed;
+ this.values = values;
+ this.flags = flags;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getSite() {
+ return site;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public HtmlParameter.Type getType() {
+ return type;
+ }
+
+ public void setType(HtmlParameter.Type type) {
+ this.type = type;
+ }
+
+ public int getTimesUsed() {
+ return timesUsed;
+ }
+
+ public void incTimesUsed() {
+ this.timesUsed++;
+ }
+
+ public Set getValues() {
+ return values;
+ }
+
+ public void addValue(String value) {
+ if (value == null) {
+ value = "";
+ }
+ this.values.add(value);
+ }
+
+ public String getValuesSummary() {
+ StringBuilder sb = new StringBuilder();
+ for (String value : values) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(value);
+ if (sb.length() > 250) {
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ public Set getFlags() {
+ return flags;
+ }
+
+ public void addFlag(String flag) {
+ this.flags.add(flag);
+ }
+
+ public String getAllFlags() {
+ StringBuilder sb = new StringBuilder();
+ for (String flag : flags) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(flag);
+ if (sb.length() > 250) {
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public int compareTo(HtmlParameterStats o) {
+ if (o == null) {
+ return 1;
+ }
+ int result = this.type.ordinal() - o.getType().ordinal();
+ if (result == 0) {
+ // Same type
+ result = this.name.compareTo(o.getName());
+ }
+ return result;
+ }
+
+ public void removeFlag(String flag) {
+ this.flags.remove(flag);
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/ParamScanner.java b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamScanner.java
new file mode 100644
index 00000000000..cfe1d5d59e3
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamScanner.java
@@ -0,0 +1,65 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import net.htmlparser.jericho.Source;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.zap.extension.pscan.PassiveScanner;
+import org.zaproxy.zap.extension.pscan.PluginPassiveScanner;
+
+public class ParamScanner implements PassiveScanner {
+
+ private final ExtensionParams2 extParams;
+
+ public ParamScanner(ExtensionParams2 extParams) {
+ this.extParams = extParams;
+ }
+
+ @Override
+ public void scanHttpRequestSend(HttpMessage msg, int id) {
+ extParams.onHttpRequestSend(msg);
+ }
+
+ @Override
+ public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) {
+ extParams.onHttpResponseReceive(msg);
+ }
+
+ @Override
+ public String getName() {
+ return "Parameter Scanner";
+ }
+
+ @Override
+ public boolean isEnabled() {
+ // Always enabled
+ return true;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ // Ignore
+ }
+
+ @Override
+ public boolean appliesToHistoryType(int historyType) {
+ return PluginPassiveScanner.getDefaultHistoryTypes().contains(historyType);
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsAPI.java b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsAPI.java
new file mode 100644
index 00000000000..b1266ea5901
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsAPI.java
@@ -0,0 +1,113 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import net.sf.json.JSONObject;
+import org.zaproxy.zap.extension.api.ApiException;
+import org.zaproxy.zap.extension.api.ApiImplementor;
+import org.zaproxy.zap.extension.api.ApiResponse;
+import org.zaproxy.zap.extension.api.ApiResponseElement;
+import org.zaproxy.zap.extension.api.ApiResponseList;
+import org.zaproxy.zap.extension.api.ApiResponseSet;
+import org.zaproxy.zap.extension.api.ApiView;
+import org.zaproxy.zap.utils.ApiUtils;
+
+public class ParamsAPI extends ApiImplementor {
+
+ private static final String PREFIX = "params";
+ private static final String VIEW_PARAMS = "params";
+ private static final String VIEW_PARAMS_PARAM_SITE = "site";
+
+ private ExtensionParams2 extension;
+
+ public ParamsAPI(ExtensionParams2 extension) {
+ this.extension = extension;
+ this.addApiView(
+ new ApiView(VIEW_PARAMS, new String[] {}, new String[] {VIEW_PARAMS_PARAM_SITE}));
+ }
+
+ @Override
+ public String getPrefix() {
+ return PREFIX;
+ }
+
+ @Override
+ public ApiResponse handleApiView(String name, JSONObject params) throws ApiException {
+ if (VIEW_PARAMS.equals(name)) {
+ ApiResponseList result = new ApiResponseList("Parameters");
+ if (params.containsKey(VIEW_PARAMS_PARAM_SITE)) {
+ String paramSite = params.getString(VIEW_PARAMS_PARAM_SITE);
+ if (!paramSite.isEmpty()) {
+ String site = ApiUtils.getAuthority(paramSite);
+ if (!extension.hasSite(site)) {
+ throw new ApiException(ApiException.Type.DOES_NOT_EXIST, paramSite);
+ }
+
+ if (extension.hasParameters(site)) {
+ result.addItem(
+ createSiteParamStatsResponse(extension.getSiteParameters(site)));
+ }
+ return result;
+ }
+ }
+
+ Collection siteParams = extension.getAllSiteParameters();
+ for (SiteParameters siteParam : siteParams) {
+ result.addItem(createSiteParamStatsResponse(siteParam));
+ }
+ return result;
+
+ } else {
+ throw new ApiException(ApiException.Type.BAD_VIEW);
+ }
+ }
+
+ private static ApiResponseList createSiteParamStatsResponse(SiteParameters siteParam) {
+ ApiResponseList stats = new ApiResponseList("Parameter");
+ for (HtmlParameterStats param : siteParam.getParams()) {
+ Map map = new HashMap<>();
+ map.put("site", param.getSite());
+ map.put("name", param.getName());
+ map.put("type", param.getType().name());
+ map.put("timesUsed", String.valueOf(param.getTimesUsed()));
+ stats.addItem(new ApiResponseSet<>("Stats", map));
+
+ ApiResponseList flags = new ApiResponseList("Flags");
+ for (String flag : param.getFlags()) {
+ flags.addItem(new ApiResponseElement("Flag", flag));
+ }
+ if (param.getFlags().size() > 0) {
+ stats.addItem(flags);
+ }
+
+ ApiResponseList vals = new ApiResponseList("Values");
+ for (String value : param.getValues()) {
+ vals.addItem(new ApiResponseElement("Value", value));
+ }
+ if (param.getValues().size() > 0) {
+ stats.addItem(vals);
+ }
+ }
+ return stats;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsPanel.java b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsPanel.java
new file mode 100644
index 00000000000..a74f8fcdbf0
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsPanel.java
@@ -0,0 +1,397 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.event.KeyEvent;
+import javax.swing.ImageIcon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import org.jdesktop.swingx.JXTable;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.AbstractPanel;
+import org.parosproxy.paros.model.SiteNode;
+import org.parosproxy.paros.network.HtmlParameter;
+import org.parosproxy.paros.view.View;
+import org.zaproxy.zap.utils.SortedComboBoxModel;
+import org.zaproxy.zap.utils.TableExportButton;
+import org.zaproxy.zap.view.ScanPanel;
+
+@SuppressWarnings("serial")
+public class ParamsPanel extends AbstractPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final String PANEL_NAME = "params";
+
+ private ExtensionParams2 extension = null;
+ private JPanel panelCommand = null;
+ private JToolBar panelToolbar = null;
+ private JScrollPane jScrollPane = null;
+
+ private String currentSite = null;
+ private JComboBox siteSelect = null;
+ private SortedComboBoxModel siteModel = new SortedComboBoxModel<>();
+ // private JButton optionsButton = null;
+
+ private JXTable paramsTable = null;
+ private ParamsTableModel paramsModel = new ParamsTableModel();
+ private TableExportButton exportButton = null;
+
+ // private static Log log = LogFactory.getLog(ParamsPanel.class);
+
+ public ParamsPanel(ExtensionParams2 extension) {
+ super();
+ this.extension = extension;
+ initialize();
+ }
+
+ /** This method initializes this */
+ private void initialize() {
+ this.setLayout(new CardLayout());
+ this.setSize(474, 251);
+ this.setName(Constant.messages.getString("params.panel.title"));
+ this.setIcon(
+ new ImageIcon(
+ ParamsPanel.class.getResource("/resource/icon/16/179.png"))); // 'form' icon
+ this.setDefaultAccelerator(
+ extension
+ .getView()
+ .getMenuShortcutKeyStroke(KeyEvent.VK_P, KeyEvent.SHIFT_DOWN_MASK, false));
+ this.setMnemonic(Constant.messages.getChar("params.panel.mnemonic"));
+ this.add(getPanelCommand(), getPanelCommand().getName());
+ }
+
+ /**
+ * This method initializes panelCommand
+ *
+ * @return javax.swing.JPanel
+ */
+ private javax.swing.JPanel getPanelCommand() {
+ if (panelCommand == null) {
+
+ panelCommand = new javax.swing.JPanel();
+ panelCommand.setLayout(new java.awt.GridBagLayout());
+ panelCommand.setName("Params");
+
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
+
+ gridBagConstraints1.gridx = 0;
+ gridBagConstraints1.gridy = 0;
+ gridBagConstraints1.insets = new java.awt.Insets(2, 2, 2, 2);
+ gridBagConstraints1.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints1.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints1.weightx = 1.0D;
+ gridBagConstraints2.gridx = 0;
+ gridBagConstraints2.gridy = 1;
+ gridBagConstraints2.weightx = 1.0;
+ gridBagConstraints2.weighty = 1.0;
+ gridBagConstraints2.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints2.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraints2.anchor = java.awt.GridBagConstraints.NORTHWEST;
+
+ panelCommand.add(this.getPanelToolbar(), gridBagConstraints1);
+ panelCommand.add(getJScrollPane(), gridBagConstraints2);
+ }
+ return panelCommand;
+ }
+
+ private javax.swing.JToolBar getPanelToolbar() {
+ if (panelToolbar == null) {
+
+ panelToolbar = new javax.swing.JToolBar();
+ panelToolbar.setLayout(new java.awt.GridBagLayout());
+ panelToolbar.setEnabled(true);
+ panelToolbar.setFloatable(false);
+ panelToolbar.setRollover(true);
+ panelToolbar.setPreferredSize(new java.awt.Dimension(800, 30));
+ panelToolbar.setName("ParamsToolbar");
+
+ GridBagConstraints gridBagConstraints0 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraints3 = new GridBagConstraints();
+ GridBagConstraints gridBagConstraintsx = new GridBagConstraints();
+
+ gridBagConstraints0.gridx = 0;
+ gridBagConstraints0.gridy = 0;
+ gridBagConstraints0.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraints0.anchor = java.awt.GridBagConstraints.WEST;
+
+ gridBagConstraints1.gridx = 1;
+ gridBagConstraints1.gridy = 0;
+ gridBagConstraints1.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraints1.anchor = java.awt.GridBagConstraints.WEST;
+
+ gridBagConstraints2.gridx = 2;
+ gridBagConstraints2.gridy = 0;
+ gridBagConstraints2.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraints2.anchor = java.awt.GridBagConstraints.WEST;
+
+ gridBagConstraints3.gridx = 3;
+ gridBagConstraints3.gridy = 0;
+ gridBagConstraints3.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraints3.anchor = java.awt.GridBagConstraints.WEST;
+
+ gridBagConstraintsx.gridx = 4;
+ gridBagConstraintsx.gridy = 0;
+ gridBagConstraintsx.weightx = 1.0;
+ gridBagConstraintsx.weighty = 1.0;
+ gridBagConstraintsx.insets = new java.awt.Insets(0, 0, 0, 0);
+ gridBagConstraintsx.anchor = java.awt.GridBagConstraints.EAST;
+ gridBagConstraintsx.fill = java.awt.GridBagConstraints.HORIZONTAL;
+
+ JLabel t1 = new JLabel();
+
+ // panelToolbar.add(getOptionsButton(), gridBagConstraints0);
+
+ panelToolbar.add(
+ new JLabel(Constant.messages.getString("params.toolbar.site.label")),
+ gridBagConstraints1);
+ panelToolbar.add(getSiteSelect(), gridBagConstraints2);
+ panelToolbar.add(getExportButton(), gridBagConstraints3);
+
+ panelToolbar.add(t1, gridBagConstraintsx);
+ }
+ return panelToolbar;
+ }
+
+ private TableExportButton getExportButton() {
+ if (exportButton == null) {
+ exportButton = new TableExportButton<>(getParamsTable());
+ }
+ return exportButton;
+ }
+
+ /*
+ * Displaying the ANTI CSRF options might not actually make that much sense...
+ private JButton getOptionsButton() {
+ if (optionsButton == null) {
+ optionsButton = new JButton();
+ optionsButton.setToolTipText(Constant.messages.getString("params.toolbar.button.options"));
+ optionsButton.setIcon(new ImageIcon(ParamsPanel.class.getResource("/resource/icon/16/041.png"))); // 'Gears' icon
+ optionsButton.setEnabled(false);
+ optionsButton.addActionListener(new ActionListener () {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Control.getSingleton().getMenuToolsControl().options(Constant.messages.getString("options.acsrf.title"));
+ }
+
+ });
+
+ }
+ return optionsButton;
+ }
+ */
+
+ private JScrollPane getJScrollPane() {
+ if (jScrollPane == null) {
+ jScrollPane = new JScrollPane();
+ jScrollPane.setViewportView(getParamsTable());
+ }
+ return jScrollPane;
+ }
+
+ private void setParamsTableColumnSizes() {
+
+ for (int i = 0; i < paramsTable.getColumnCount(); i++) {
+ DefaultTableColumnModel colModel =
+ (DefaultTableColumnModel) paramsTable.getColumnModel();
+ TableColumn col = colModel.getColumn(i);
+ int width = 0;
+
+ TableCellRenderer renderer = col.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = paramsTable.getTableHeader().getDefaultRenderer();
+ }
+ Component comp =
+ renderer.getTableCellRendererComponent(
+ paramsTable, col.getHeaderValue(), false, false, 0, 0);
+ width = comp.getPreferredSize().width;
+ col.setPreferredWidth(width + 2);
+ }
+ paramsTable.getColumnModel().getColumn(6).setPreferredWidth(999); // value
+ }
+
+ protected JXTable getParamsTable() {
+ if (paramsTable == null) {
+ paramsTable = new JXTable(paramsModel);
+
+ paramsTable.setColumnSelectionAllowed(false);
+ paramsTable.setCellSelectionEnabled(false);
+ paramsTable.setRowSelectionAllowed(true);
+ paramsTable.setAutoCreateRowSorter(true);
+ paramsTable.setColumnControlVisible(true);
+
+ this.setParamsTableColumnSizes();
+
+ paramsTable.setName(PANEL_NAME);
+ paramsTable.setDoubleBuffered(true);
+ paramsTable.addMouseListener(
+ new java.awt.event.MouseAdapter() {
+ @Override
+ public void mousePressed(java.awt.event.MouseEvent e) {
+ showPopupMenuIfTriggered(e);
+ }
+
+ @Override
+ public void mouseReleased(java.awt.event.MouseEvent e) {
+ showPopupMenuIfTriggered(e);
+ }
+
+ private void showPopupMenuIfTriggered(java.awt.event.MouseEvent e) {
+ if (e.isPopupTrigger()) {
+
+ // Select table item
+ int row = paramsTable.rowAtPoint(e.getPoint());
+ if (row < 0
+ || !paramsTable.getSelectionModel().isSelectedIndex(row)) {
+ paramsTable.getSelectionModel().clearSelection();
+ if (row >= 0) {
+ paramsTable
+ .getSelectionModel()
+ .setSelectionInterval(row, row);
+ }
+ }
+
+ View.getSingleton()
+ .getPopupMenu()
+ .show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+ });
+ }
+ return paramsTable;
+ }
+
+ private JComboBox getSiteSelect() {
+ if (siteSelect == null) {
+ siteSelect = new JComboBox<>(siteModel);
+ siteSelect.addItem(Constant.messages.getString("params.toolbar.site.select"));
+ siteSelect.setSelectedIndex(0);
+
+ siteSelect.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+
+ String item = (String) siteSelect.getSelectedItem();
+ if (item != null && siteSelect.getSelectedIndex() > 0) {
+ siteSelected(item);
+ }
+ }
+ });
+ }
+ return siteSelect;
+ }
+
+ public void addSite(String site) {
+ site = ScanPanel.cleanSiteName(site, true);
+ if (siteModel.getIndexOf(site) < 0) {
+ siteModel.addElement(site);
+ if (siteModel.getSize() == 2 && currentSite == null) {
+ // First site added, automatically select it
+ this.getSiteSelect().setSelectedIndex(1);
+ siteSelected(site);
+ }
+ }
+ }
+
+ private void siteSelected(String site) {
+ site = ScanPanel.cleanSiteName(site, true);
+ if (!site.equals(currentSite)) {
+ siteModel.setSelectedItem(site);
+
+ paramsModel = extension.getSiteParameters(site).getModel();
+ this.getParamsTable().setModel(paramsModel);
+
+ this.setParamsTableColumnSizes();
+
+ currentSite = site;
+ }
+ }
+
+ public void nodeSelected(SiteNode node) {
+ if (node != null) {
+ siteSelected(ScanPanel.cleanSiteName(node, true));
+ }
+ }
+
+ public void reset() {
+ currentSite = null;
+
+ siteModel.removeAllElements();
+ siteSelect.addItem(Constant.messages.getString("params.toolbar.site.select"));
+ siteSelect.setSelectedIndex(0);
+
+ paramsModel.removeAllElements();
+ paramsModel.fireTableDataChanged();
+
+ paramsTable.setModel(paramsModel);
+ }
+
+ /**
+ * Gets the current selected site.
+ *
+ * @return the current site
+ */
+ public String getCurrentSite() {
+ return currentSite;
+ }
+
+ protected HtmlParameterStats getSelectedParam() {
+ int selectedRow = this.getParamsTable().getSelectedRow();
+ if (selectedRow == -1) {
+ return null;
+ }
+
+ // TODO type is localized :(
+ String type = (String) this.getParamsTable().getValueAt(selectedRow, 0);
+ String name = (String) this.getParamsTable().getValueAt(selectedRow, 1);
+
+ SiteParameters sps = extension.getSiteParameters(currentSite);
+ if (sps != null) {
+ return sps.getParam(HtmlParameter.Type.valueOf(type.toLowerCase()), name); // TODO HACK!
+ }
+ return null;
+ }
+
+ /**
+ * Tells whether or not only one of the parameters is selected.
+ *
+ * @return {@code true} if only one parameter is selected, {@code false} otherwise.
+ * @see #getSelectedParam()
+ * @since 2.6.0
+ */
+ boolean isOnlyOneParamSelected() {
+ return getParamsTable().getSelectedRowCount() == 1;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsTableModel.java b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsTableModel.java
new file mode 100644
index 00000000000..c2efc6cd58c
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/ParamsTableModel.java
@@ -0,0 +1,184 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Vector;
+import javax.swing.table.AbstractTableModel;
+import org.parosproxy.paros.Constant;
+
+@SuppressWarnings("serial")
+public class ParamsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+ private static final int COLUMN_COUNT = 7;
+
+ private final Vector columnNames;
+ private List paramStats;
+
+ private int lastAddedRow;
+ private int lastEditedRow;
+
+ public ParamsTableModel() {
+ super();
+ columnNames = new Vector<>(COLUMN_COUNT);
+ columnNames.add(Constant.messages.getString("params.table.header.type"));
+ columnNames.add(Constant.messages.getString("params.table.header.name"));
+ columnNames.add(Constant.messages.getString("params.table.header.used"));
+ columnNames.add(Constant.messages.getString("params.table.header.numvals"));
+ columnNames.add(Constant.messages.getString("params.table.header.pcchange"));
+ columnNames.add(Constant.messages.getString("params.table.header.flags"));
+ columnNames.add(Constant.messages.getString("params.table.header.values"));
+
+ paramStats = Collections.synchronizedList(new ArrayList<>());
+
+ lastAddedRow = -1;
+ lastEditedRow = -1;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return COLUMN_COUNT;
+ }
+
+ @Override
+ public int getRowCount() {
+ return paramStats.size();
+ }
+
+ @Override
+ public String getColumnName(int col) {
+ return columnNames.get(col);
+ }
+
+ public List getColumnNames() {
+ return new ArrayList<>(columnNames);
+ }
+
+ @Override
+ public Object getValueAt(int row, int col) {
+ Object obj = null;
+ if (row >= paramStats.size()) {
+ return null;
+ }
+ // TODO this doesn't work if resorted??
+ HtmlParameterStats param = paramStats.get(row);
+ switch (col) {
+ case 0:
+ obj = Constant.messages.getString("params.type." + param.getType().name());
+ break;
+ case 1:
+ obj = param.getName();
+ break;
+ case 2:
+ obj = param.getTimesUsed();
+ break;
+ case 3:
+ obj = param.getValues().size();
+ break;
+ case 4:
+ obj = getPercentChange(param);
+ break;
+ case 5:
+ obj = param.getAllFlags();
+ break;
+ case 6:
+ obj = param.getValuesSummary();
+ break;
+ }
+ return obj;
+ }
+
+ private int getPercentChange(HtmlParameterStats param) {
+ if (param.getValues().size() == 1) {
+ return 0;
+ }
+ return (param.getValues().size() * 100 / param.getTimesUsed());
+ }
+
+ public HtmlParameterStats getHtmlParameterStatsAtRow(int row) {
+ // TODO this doesn't work if resorted??
+ return paramStats.get(row);
+ }
+
+ public synchronized void addHtmlParameterStats(HtmlParameterStats param) {
+ lastAddedRow = -1;
+
+ for (int i = 0; i < paramStats.size(); i++) {
+ int cmp = param.compareTo(paramStats.get(i));
+ if (cmp < 0) {
+ paramStats.add(i, param);
+ this.fireTableRowsInserted(i, i);
+
+ lastAddedRow = i;
+ return;
+ } else if (cmp == 0) {
+ // Already matches, so ignore
+ lastAddedRow = i;
+ return;
+ }
+ }
+
+ paramStats.add(param);
+ this.fireTableRowsInserted(paramStats.size() - 1, paramStats.size() - 1);
+
+ lastAddedRow = paramStats.size() - 1;
+ }
+
+ public int getLastAddedRow() {
+ return lastAddedRow;
+ }
+
+ public int getLastEditedRow() {
+ return lastEditedRow;
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return false;
+ }
+
+ @Override
+ public Class extends Object> getColumnClass(int c) {
+ switch (c) {
+ case 0:
+ return String.class;
+ case 1:
+ return String.class;
+ case 2:
+ return Integer.class;
+ case 3:
+ return Integer.class;
+ case 4:
+ return Integer.class;
+ case 5:
+ return String.class;
+ case 6:
+ return String.class;
+ }
+ return null;
+ }
+
+ public void removeAllElements() {
+ paramStats.clear();
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddAntiCSRF.java b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddAntiCSRF.java
new file mode 100644
index 00000000000..e480b1d05c3
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddAntiCSRF.java
@@ -0,0 +1,75 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+import org.parosproxy.paros.network.HtmlParameter;
+
+@SuppressWarnings("serial")
+public class PopupMenuAddAntiCSRF extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionParams2 extension;
+
+ public PopupMenuAddAntiCSRF() {
+ super(Constant.messages.getString("params.anticsrf.add.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+
+ extension.addAntiCsrfToken();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionParams2 extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(ParamsPanel.PANEL_NAME)) {
+ if (!extension.getParamsPanel().isOnlyOneParamSelected()) {
+ this.setEnabled(false);
+ return true;
+ }
+
+ HtmlParameterStats item = extension.getParamsPanel().getSelectedParam();
+ // Note that only form params are currently supported
+ if (item != null && !item.getFlags().contains(HtmlParameter.Flags.anticsrf.name())) {
+ this.setEnabled(true);
+ return true;
+ }
+ this.setEnabled(false);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSafe() {
+ return true;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddSession.java b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddSession.java
new file mode 100644
index 00000000000..39f1b097bc6
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuAddSession.java
@@ -0,0 +1,81 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+import org.parosproxy.paros.network.HtmlParameter;
+
+@SuppressWarnings("serial")
+public class PopupMenuAddSession extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionParams2 extension;
+
+ public PopupMenuAddSession() {
+ super(Constant.messages.getString("params.session.add.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+
+ extension.addSessionToken();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionParams2 extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(ParamsPanel.PANEL_NAME)) {
+ if (!extension.getParamsPanel().isOnlyOneParamSelected()) {
+ this.setEnabled(false);
+ return true;
+ }
+
+ HtmlParameterStats item = extension.getParamsPanel().getSelectedParam();
+ // Note that only cookie params are currently supported
+ // TODO: Add support for URL tokens
+ if (item != null) {
+ if (HtmlParameter.Type.cookie.equals(item.getType())) {
+ if (!item.getFlags().contains(HtmlParameter.Flags.session.name())) {
+ this.setEnabled(true);
+ return true;
+ }
+ return false;
+ }
+ this.setEnabled(false);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSafe() {
+ return true;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuParamSearch.java b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuParamSearch.java
new file mode 100644
index 00000000000..4b7456da7a1
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuParamSearch.java
@@ -0,0 +1,62 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+
+@SuppressWarnings("serial")
+public class PopupMenuParamSearch extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionParams2 extension;
+
+ public PopupMenuParamSearch() {
+ super(Constant.messages.getString("params.search.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ extension.searchForSelectedParam();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionParams2 extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(ParamsPanel.PANEL_NAME)) {
+ this.setEnabled(extension.getParamsPanel().isOnlyOneParamSelected());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSafe() {
+ return true;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveAntiCSRF.java b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveAntiCSRF.java
new file mode 100644
index 00000000000..8cac77887ea
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveAntiCSRF.java
@@ -0,0 +1,71 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+import org.parosproxy.paros.network.HtmlParameter;
+
+@SuppressWarnings("serial")
+public class PopupMenuRemoveAntiCSRF extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionParams2 extension;
+
+ public PopupMenuRemoveAntiCSRF() {
+ super(Constant.messages.getString("params.anticsrf.remove.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+
+ extension.removeAntiCsrfToken();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionParams2 extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(ParamsPanel.PANEL_NAME)) {
+ if (!extension.getParamsPanel().isOnlyOneParamSelected()) {
+ return false;
+ }
+
+ HtmlParameterStats item = extension.getParamsPanel().getSelectedParam();
+ if (item != null && item.getFlags().contains(HtmlParameter.Flags.anticsrf.name())) {
+ this.setEnabled(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSafe() {
+ return true;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveSession.java b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveSession.java
new file mode 100644
index 00000000000..85a5ec8b894
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/PopupMenuRemoveSession.java
@@ -0,0 +1,71 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.awt.Component;
+import org.parosproxy.paros.Constant;
+import org.parosproxy.paros.extension.ExtensionPopupMenuItem;
+import org.parosproxy.paros.network.HtmlParameter;
+
+@SuppressWarnings("serial")
+public class PopupMenuRemoveSession extends ExtensionPopupMenuItem {
+
+ private static final long serialVersionUID = 1L;
+
+ private ExtensionParams2 extension;
+
+ public PopupMenuRemoveSession() {
+ super(Constant.messages.getString("params.session.remove.popup"));
+ this.addActionListener(
+ new java.awt.event.ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+
+ extension.removeSessionToken();
+ }
+ });
+ }
+
+ public void setExtension(ExtensionParams2 extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public boolean isEnableForComponent(Component invoker) {
+ if (invoker.getName() != null && invoker.getName().equals(ParamsPanel.PANEL_NAME)) {
+ if (!extension.getParamsPanel().isOnlyOneParamSelected()) {
+ return false;
+ }
+
+ HtmlParameterStats item = extension.getParamsPanel().getSelectedParam();
+ if (item != null && item.getFlags().contains(HtmlParameter.Flags.session.name())) {
+ this.setEnabled(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSafe() {
+ return true;
+ }
+}
diff --git a/addOns/params/src/main/java/org/zaproxy/addon/params/SiteParameters.java b/addOns/params/src/main/java/org/zaproxy/addon/params/SiteParameters.java
new file mode 100644
index 00000000000..1c1c2b74f0c
--- /dev/null
+++ b/addOns/params/src/main/java/org/zaproxy/addon/params/SiteParameters.java
@@ -0,0 +1,229 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2011 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.parosproxy.paros.db.RecordParam;
+import org.parosproxy.paros.network.HtmlParameter;
+import org.parosproxy.paros.network.HtmlParameter.Type;
+import org.parosproxy.paros.network.HttpMessage;
+import org.zaproxy.zap.extension.httpsessions.ExtensionHttpSessions;
+import org.zaproxy.zap.utils.ThreadUtils;
+
+public class SiteParameters {
+ private ExtensionParams2 extension;
+ private String site;
+ private ParamsTableModel model = new ParamsTableModel();
+ private Map cookieParams = new HashMap<>();
+ private Map urlParams = new HashMap<>();
+ private Map formParams = new HashMap<>();
+ private Map headerParams = new HashMap<>();
+ private Map multipartParams = new HashMap<>();
+
+ public SiteParameters(ExtensionParams2 extension, String site) {
+ this.extension = extension;
+ this.site = site;
+ }
+
+ public String getSite() {
+ return site;
+ }
+
+ public void setSite(String site) {
+ this.site = site;
+ }
+
+ /**
+ * Tells whether or not this site has any parameters (cookies, query, form parameters, or
+ * response header fields).
+ *
+ * @return {@code true} if this site has parameters, {@code false} otherwise.
+ * @since 2.5.0
+ */
+ public boolean hasParams() {
+ return !cookieParams.isEmpty()
+ || !urlParams.isEmpty()
+ || !formParams.isEmpty()
+ || !headerParams.isEmpty()
+ || !multipartParams.isEmpty();
+ }
+
+ public HtmlParameterStats getParam(HtmlParameter.Type type, String name) {
+ switch (type) {
+ case cookie:
+ return cookieParams.get(name);
+ case url:
+ return urlParams.get(name);
+ case form:
+ return formParams.get(name);
+ case header:
+ return headerParams.get(name);
+ case multipart:
+ return multipartParams.get(name);
+ }
+ return null;
+ }
+
+ public List getParams(HtmlParameter.Type type) {
+ List params = new ArrayList<>();
+ switch (type) {
+ case cookie:
+ params.addAll(this.cookieParams.values());
+ break;
+ case url:
+ params.addAll(this.urlParams.values());
+ break;
+ case form:
+ params.addAll(this.formParams.values());
+ break;
+ case header:
+ params.addAll(this.headerParams.values());
+ break;
+ case multipart:
+ params.addAll(this.multipartParams.values());
+ break;
+ }
+ return params;
+ }
+
+ public List getParams() {
+ List params = new ArrayList<>();
+ params.addAll(this.cookieParams.values());
+ params.addAll(this.urlParams.values());
+ params.addAll(this.formParams.values());
+ params.addAll(this.headerParams.values());
+ params.addAll(this.multipartParams.values());
+ return params;
+ }
+
+ public synchronized HtmlParameterStats addParam(
+ String site, HtmlParameter param, HttpMessage msg) {
+ Map params = null;
+ HtmlParameterStats p;
+
+ switch (param.getType()) {
+ case cookie:
+ params = cookieParams;
+ break;
+ case url:
+ params = urlParams;
+ break;
+ case form:
+ params = formParams;
+ break;
+ case header:
+ params = headerParams;
+ break;
+ case multipart:
+ params = multipartParams;
+ break;
+ }
+
+ if (params != null && params.containsKey(param.getName())) {
+ p = params.get(param.getName());
+ p.incTimesUsed();
+ p.addValue(param.getValue());
+ } else {
+ // It's a new parameter
+ p =
+ new HtmlParameterStats(
+ site,
+ param.getName(),
+ param.getType(),
+ param.getValue(),
+ param.getFlags());
+
+ // If the HttpSessions extension is active, check if the token is a session token and,
+ // if it is, mark it so
+ ExtensionHttpSessions extSession = extension.getExtensionHttpSessions();
+ if (extSession != null) {
+ if (param.getType().equals(Type.cookie)
+ && extSession.isSessionToken(site, param.getName())) {
+ // Only Cookies can be session params
+ // TODO: Add support for URL tokens
+ p.addFlag(HtmlParameter.Flags.session.name());
+ }
+ }
+
+ if (params == null) {
+ params = new HashMap<>();
+ }
+ params.put(param.getName(), p);
+ ThreadUtils.invokeLater(() -> model.addHtmlParameterStats(p));
+ }
+ return p;
+ }
+
+ public ParamsTableModel getModel() {
+ return model;
+ }
+
+ private Set stringToSet(String str) {
+ Set set = new HashSet<>();
+ // TODO handle encoded commas?
+ String[] array = str.split(",");
+ for (String s : array) {
+ set.add(s);
+ }
+ return set;
+ }
+
+ public synchronized void addParam(String site2, RecordParam param) {
+ Map params = null;
+
+ HtmlParameter.Type type = HtmlParameter.Type.valueOf(param.getType());
+ switch (type) {
+ case cookie:
+ params = cookieParams;
+ break;
+ case url:
+ params = urlParams;
+ break;
+ case form:
+ params = formParams;
+ break;
+ case header:
+ params = headerParams;
+ break;
+ case multipart:
+ params = multipartParams;
+ break;
+ }
+ // These should all be new
+ HtmlParameterStats p =
+ new HtmlParameterStats(
+ param.getParamId(),
+ site,
+ param.getName(),
+ param.getType(),
+ param.getUsed(),
+ stringToSet(param.getValues()),
+ stringToSet(param.getFlags()));
+ if (params != null) {
+ params.put(param.getName(), p);
+ ThreadUtils.invokeLater(() -> model.addHtmlParameterStats(p));
+ }
+ }
+}
diff --git a/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/contents/params.html b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/contents/params.html
new file mode 100644
index 00000000000..197824bd991
--- /dev/null
+++ b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/contents/params.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+Params tab
+
+
+
+Params tab
+
+This shows a summary of the parameters and response header fields a site uses.
+Sites can be selected via the toolbar or the Sites tab.
+For each parameter you can see:
+
+
+| |
+The type - Cookie, FORM, URL, or Header |
+| |
+The name of the parameter (or response header) |
+| |
+The number of times it has been used |
+| |
+The number of unique values |
+| |
+The percentage change, where 0 means only one value has been used and 100 means all values are unique |
+| |
+The flags - including cookie flags and anticsrf and session |
+| |
+Some of the values - the full set of values may not all be visible |
+
+
+Right click menu
+Right clicking on a node will bring up a menu which will allow you to:
+
+Search
+This will show all examples of the parameter selected in the Search tab.
+
+Flag as Anti CSRF token
+This will flag the parameter as an Anti CSRF token.
+
+Unflag as Anti CSRF token
+This will remove the Anti CSRF token flag from the parameter.
+
+Flag as Session token
+ This will mark the parameter as a Session token for the current Site and will notify the
+ HTTP Sessions tool accordingly.
+
+
+Unflag as Session token
+This will unmark the parameter as a Session token for the current site and will notify the
+ HTTP Sessions tool accordingly.
+
+
+
+
diff --git a/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/helpset.hs b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/helpset.hs
new file mode 100644
index 00000000000..84d5bdf0e9e
--- /dev/null
+++ b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/helpset.hs
@@ -0,0 +1,41 @@
+
+
+
+ Spider Add-On
+
+
+ params
+
+
+
+
+ TOC
+
+ org.zaproxy.zap.extension.help.ZapTocView
+ toc.xml
+
+
+
+ Index
+
+ javax.help.IndexView
+ index.xml
+
+
+
+ Search
+
+ javax.help.SearchView
+
+ JavaHelpSearch
+
+
+
+
+ Favorites
+
+ javax.help.FavoritesView
+
+
diff --git a/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/index.xml b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/index.xml
new file mode 100644
index 00000000000..b6f1f37f947
--- /dev/null
+++ b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/index.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/map.jhm b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/map.jhm
new file mode 100644
index 00000000000..62f2d47fbaf
--- /dev/null
+++ b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/map.jhm
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/toc.xml b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/toc.xml
new file mode 100644
index 00000000000..614e154ff7a
--- /dev/null
+++ b/addOns/params/src/main/javahelp/org/zaproxy/addon/parans/resources/help/toc.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addOns/params/src/main/resources/org/zaproxy/addon/params/resources/Messages.properties b/addOns/params/src/main/resources/org/zaproxy/addon/params/resources/Messages.properties
new file mode 100644
index 00000000000..a52f37218d1
--- /dev/null
+++ b/addOns/params/src/main/resources/org/zaproxy/addon/params/resources/Messages.properties
@@ -0,0 +1,26 @@
+params.anticsrf.add.popup = Add Name as Anti-CSRF Token
+params.anticsrf.remove.popup = Remove Name as Anti-CSRF Token
+params.api.desc =
+params.api.view.params = Shows the parameters for the specified site, or for all sites if the site is not specified
+params.api.view.params.param.site =
+params.desc = Summarise and analyse FORM and URL parameters as well as cookies and headers
+params.name = Parameters Add-on
+params.panel.mnemonic = p
+params.panel.title = Params
+params.search.popup = Search
+params.session.add.popup = Flag as Session Token
+params.session.remove.popup = Unflag as Session Token
+params.table.header.flags = Flags
+params.table.header.name = Name
+params.table.header.numvals = # Values
+params.table.header.pcchange = % Change
+params.table.header.type = Type
+params.table.header.used = Used
+params.table.header.values = Values
+params.toolbar.site.label = Site:
+params.toolbar.site.select = --Select Site--
+params.type.cookie = Cookie
+params.type.form = FORM
+params.type.header = Header
+params.type.multipart = Multipart
+params.type.url = URL
diff --git a/addOns/params/src/test/java/org/zaproxy/addon/params/ExtensionParams2UnitTest.java b/addOns/params/src/test/java/org/zaproxy/addon/params/ExtensionParams2UnitTest.java
new file mode 100644
index 00000000000..b66ce1c045b
--- /dev/null
+++ b/addOns/params/src/test/java/org/zaproxy/addon/params/ExtensionParams2UnitTest.java
@@ -0,0 +1,75 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2022 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.addon.params;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.withSettings;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.quality.Strictness;
+import org.parosproxy.paros.control.Control;
+import org.parosproxy.paros.extension.ExtensionHook;
+import org.parosproxy.paros.extension.ExtensionLoader;
+import org.parosproxy.paros.extension.SessionChangedListener;
+import org.parosproxy.paros.model.Model;
+import org.parosproxy.paros.model.Session;
+import org.zaproxy.addon.pscan.ExtensionPassiveScan2;
+import org.zaproxy.addon.pscan.PassiveScannersManager;
+
+/** Unit test for {@link ExtensionSParams2}. */
+class ExtensionParams2UnitTest {
+
+ private ExtensionParams2 extension;
+
+ @BeforeEach
+ void setUp() {
+ extension = new ExtensionParams2();
+ }
+
+ @Test
+ void shouldAddSessionChangedListenerOnHook() {
+ // Given
+ Model model = mock(Model.class, withSettings().strictness(Strictness.LENIENT));
+ Session session = mock(Session.class, withSettings().strictness(Strictness.LENIENT));
+ given(model.getSession()).willReturn(session);
+ ExtensionLoader extensionLoader =
+ mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT));
+ Control.initSingletonForTesting(model, extensionLoader);
+ ExtensionPassiveScan2 extPscan =
+ mock(ExtensionPassiveScan2.class, withSettings().strictness(Strictness.LENIENT));
+ given(extensionLoader.getExtension(ExtensionPassiveScan2.class)).willReturn(extPscan);
+ given(extPscan.getPassiveScannersManager()).willReturn(mock(PassiveScannersManager.class));
+ ExtensionHook extensionHook = mock(ExtensionHook.class);
+ // When
+ extension.hook(extensionHook);
+ // Then
+ ArgumentCaptor argument =
+ ArgumentCaptor.forClass(SessionChangedListener.class);
+ verify(extensionHook).addSessionListener(argument.capture());
+ assertThat(argument.getValue(), is(notNullValue()));
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 177cde66ebf..51b0664f385 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -69,6 +69,7 @@ var addOns =
"packpentester",
"packscanrules",
"paramdigger",
+ "params",
"plugnhack",
"postman",
"pscan",