From 3390754eca1508709f7d7c3d76b40a50362e06ba Mon Sep 17 00:00:00 2001 From: thc202 Date: Mon, 17 Nov 2025 16:24:21 +0000 Subject: [PATCH 1/2] Allow to lock scan policies Prevent the usage of scan rules that are not defined in the policy file while allowing to override the default alert threshold. Signed-off-by: thc202 --- .../paros/core/scanner/PluginFactory.java | 34 +++++++ .../ascan/PolicyAllCategoryPanel.java | 30 ++++++ .../zap/extension/ascan/ScanPolicy.java | 30 ++++++ .../zaproxy/zap/resources/Messages.properties | 1 + .../core/scanner/PluginFactoryUnitTest.java | 95 +++++++++++++++++++ .../extension/ascan/ScanPolicyUnitTest.java | 51 ++++++++++ 6 files changed, 241 insertions(+) diff --git a/zap/src/main/java/org/parosproxy/paros/core/scanner/PluginFactory.java b/zap/src/main/java/org/parosproxy/paros/core/scanner/PluginFactory.java index f158b70e4cd..6d7f0eddf0b 100644 --- a/zap/src/main/java/org/parosproxy/paros/core/scanner/PluginFactory.java +++ b/zap/src/main/java/org/parosproxy/paros/core/scanner/PluginFactory.java @@ -60,6 +60,7 @@ // ZAP: 2022/02/03 Removed loadedPlugin and unloadedPlugin. // ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging. // ZAP: 2023/01/10 Tidy up logger. +// ZAP: 2025/11/17 Support locked policy. package org.parosproxy.paros.core.scanner; import java.util.ArrayList; @@ -77,6 +78,7 @@ import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.core.scanner.Plugin.AlertThreshold; import org.zaproxy.zap.control.ExtensionFactory; public class PluginFactory { @@ -96,6 +98,7 @@ public class PluginFactory { private int totalPluginToRun = 0; private boolean init = false; private Configuration config; + private boolean locked; public PluginFactory() { super(); @@ -104,6 +107,30 @@ public PluginFactory() { config = configuration; } + /** + * Tells whether or not this factory is locked. + * + *

Locked factories automatically disable any rules not defined in the loaded configuration. + * + * @return {@code true} if the factory is locked, {@code false} otherwise. + * @since 2.17.0 + * @see #loadAllPlugin(Configuration) + */ + public boolean isLocked() { + return locked; + } + + /** + * Sets whether or not this factory should be locked. + * + * @param locked {@code true} if the factory should be locked, {@code false} otherwise. + * @since 2.17.0 + * @see #isLocked() + */ + public void setLocked(boolean locked) { + this.locked = locked; + } + private static synchronized void initPlugins() { if (loadedPlugins == null) { init(true); @@ -396,7 +423,14 @@ public synchronized void loadAllPlugin(Configuration config) { continue; } + boolean disable = + locked && !config.getKeys("plugins.p" + loadedPlugin.getId()).hasNext(); + Plugin plugin = createNewPlugin(loadedPlugin, config); + if (disable) { + plugin.setAlertThreshold(AlertThreshold.OFF); + } + LOGGER.debug( "loaded plugin {} with: Threshold={} Strength={}", plugin.getName(), diff --git a/zap/src/main/java/org/zaproxy/zap/extension/ascan/PolicyAllCategoryPanel.java b/zap/src/main/java/org/zaproxy/zap/extension/ascan/PolicyAllCategoryPanel.java index 25d4b1f5544..949e6f38c94 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/ascan/PolicyAllCategoryPanel.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/ascan/PolicyAllCategoryPanel.java @@ -28,6 +28,7 @@ // not important). // ZAP: 2019/06/01 Normalise line endings. // ZAP: 2019/06/05 Normalise format/style. +// ZAP: 2025/11/17 Support locked policy. package org.zaproxy.zap.extension.ascan; import java.awt.GridBagConstraints; @@ -44,6 +45,7 @@ import javax.swing.DefaultCellEditor; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; @@ -75,6 +77,7 @@ public class PolicyAllCategoryPanel extends AbstractParamPanel { private static final Logger LOGGER = LogManager.getLogger(PolicyAllCategoryPanel.class); private ZapTextField policyName = null; + private JCheckBox locked; private JTable tableTest = null; private JScrollPane jScrollPane = null; private AllCategoryTableModel allCategoryTableModel = null; @@ -146,6 +149,30 @@ private void initialize() { 0, GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2))); + + locked = new JCheckBox(); + locked.setSelected(policy.isLocked()); + row++; + this.add( + new JLabel(Constant.messages.getString("ascan.policy.locked.label")), + LayoutHelper.getGBC( + 0, + row, + 1, + 0.0D, + 0, + GridBagConstraints.HORIZONTAL, + new Insets(2, 2, 2, 2))); + this.add( + locked, + LayoutHelper.getGBC( + 1, + row, + 2, + 1.0D, + 0, + GridBagConstraints.HORIZONTAL, + new Insets(2, 2, 2, 2))); } row++; @@ -549,6 +576,9 @@ public void validateParam(Object obj) throws Exception { @Override public void saveParam(Object obj) throws Exception { this.policy.setName(getPolicyName().getText()); + if (locked != null) { + policy.setLocked(locked.isSelected()); + } } /** diff --git a/zap/src/main/java/org/zaproxy/zap/extension/ascan/ScanPolicy.java b/zap/src/main/java/org/zaproxy/zap/extension/ascan/ScanPolicy.java index 7493dc7c762..6cf668376a3 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/ascan/ScanPolicy.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/ascan/ScanPolicy.java @@ -39,6 +39,7 @@ public class ScanPolicy { private String name; private String statsId; private boolean readOnly; + private boolean locked; private PluginFactory pluginFactory = new PluginFactory(); private AlertThreshold defaultThreshold; private AttackStrength defaultStrength; @@ -57,10 +58,12 @@ public ScanPolicy(ZapXmlConfiguration conf) throws ConfigurationException { name = conf.getString("policy", ""); statsId = conf.getString("statsId", null); readOnly = conf.getBoolean("readonly", false); + locked = conf.getBoolean("locked", false); if (statsId == null && name.equals(Constant.messages.getString("ascan.policymgr.default.name"))) { statsId = "default"; } + pluginFactory.setLocked(locked); pluginFactory.loadAllPlugin(conf); setDefaultThreshold(getAlertThresholdFromConfig()); @@ -85,6 +88,7 @@ public void cloneInto(ScanPolicy policy) { policy.defaultThreshold = this.getDefaultThreshold(); policy.statsId = this.statsId; policy.readOnly = this.readOnly; + policy.locked = this.locked; } /** @@ -94,6 +98,7 @@ public void cloneInto(ScanPolicy policy) { */ public void saveTo(Configuration conf) throws ConfigurationException { conf.setProperty("policy", getName()); + conf.setProperty("locked", isLocked()); conf.setProperty("scanner.level", getDefaultThreshold().name()); conf.setProperty("scanner.strength", getDefaultStrength().name()); getPluginFactory().saveTo(conf); @@ -190,4 +195,29 @@ public String getStatsId() { public boolean isReadOnly() { return readOnly; } + + /** + * Tells whether or not the policy is locked. + * + *

Locked policies do not allow the use of any other scan rules than the ones defined in + * their configuration. + * + * @return {@code true} if the policy is locked, {@code false} otherwise. + * @since 2.17.0 + */ + public boolean isLocked() { + return locked; + } + + /** + * Sets whether or not this policy should be locked. + * + * @param locked {@code true} if the policy should be locked, {@code false} otherwise. + * @since 2.17.0 + * @see #isLocked() + */ + public void setLocked(boolean locked) { + this.locked = locked; + pluginFactory.setLocked(locked); + } } diff --git a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties index 66467353cda..f707483fe90 100644 --- a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties +++ b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties @@ -539,6 +539,7 @@ ascan.policy.level.low = Low ascan.policy.level.medium = Medium ascan.policy.level.off = OFF ascan.policy.load.error = Failed to load policy file, see log for detail +ascan.policy.locked.label = Locked: ascan.policy.name.default = Default Policy ascan.policy.name.label = Policy: ascan.policy.namedialog.name.label = New Policy Name: diff --git a/zap/src/test/java/org/parosproxy/paros/core/scanner/PluginFactoryUnitTest.java b/zap/src/test/java/org/parosproxy/paros/core/scanner/PluginFactoryUnitTest.java index 7f64eaaf7ec..a72fb12be06 100644 --- a/zap/src/test/java/org/parosproxy/paros/core/scanner/PluginFactoryUnitTest.java +++ b/zap/src/test/java/org/parosproxy/paros/core/scanner/PluginFactoryUnitTest.java @@ -36,11 +36,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.quality.Strictness; import org.parosproxy.paros.Constant; import org.zaproxy.zap.control.AddOn; +import org.zaproxy.zap.extension.ascan.ScanPolicy; import org.zaproxy.zap.utils.I18N; +import org.zaproxy.zap.utils.ZapXmlConfiguration; /** Unit test for {@link PluginFactory}. */ @ExtendWith(MockitoExtension.class) @@ -183,6 +187,97 @@ void shouldHaveDifferentPluginInstancesPerPluginFactory() { is(not(sameInstance(otherPluginFactory.getAllPlugin().get(0))))); } + @Test + void shouldNotBeLockedByDefault() { + // Given + PluginFactory pluginFactory = new PluginFactory(); + // When / Then + assertThat(pluginFactory.isLocked(), is(equalTo(false))); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldSetLocked(boolean locked) { + // Given + PluginFactory pluginFactory = new PluginFactory(); + // When + pluginFactory.setLocked(locked); + // Then + assertThat(pluginFactory.isLocked(), is(equalTo(locked))); + } + + @Test + void shouldDisableUnconfiguredPluginsWhenLocked() { + // Given + PluginFactory pluginFactory = new PluginFactory(); + pluginFactory.setLocked(true); + ZapXmlConfiguration conf = configWithPlugin(); + // When + pluginFactory.loadAllPlugin(conf); + // Then + List plugins = pluginFactory.getAllPlugin(); + assertThat(plugins.get(0).getId(), is(equalTo(1))); + assertThat(plugins.get(0).isEnabled(), is(equalTo(true))); + assertThat(plugins.get(1).getId(), is(equalTo(2))); + assertThat(plugins.get(1).isEnabled(), is(equalTo(false))); + } + + @Test + void shouldDisableUnconfiguredPluginsWhenLockedThroughScanPolicy() throws Exception { + // Given + ZapXmlConfiguration conf = configWithPlugin(); + conf.setProperty("locked", true); + // When + ScanPolicy scanPolicy = new ScanPolicy(conf); + // Then + List plugins = scanPolicy.getPluginFactory().getAllPlugin(); + assertThat(plugins.get(0).getId(), is(equalTo(1))); + assertThat(plugins.get(0).isEnabled(), is(equalTo(true))); + assertThat(plugins.get(1).getId(), is(equalTo(2))); + assertThat(plugins.get(1).isEnabled(), is(equalTo(false))); + } + + static ZapXmlConfiguration configWithPlugin() { + AbstractPlugin plugin1 = createAbstractPlugin(1); + AbstractPlugin plugin2 = createAbstractPlugin(2); + PluginFactory.loadedPlugin(plugin1); + PluginFactory.loadedPlugin(plugin2); + ZapXmlConfiguration conf = new ZapXmlConfiguration(); + conf.setProperty("plugins.p1.level", "DEFAULT"); + return conf; + } + + @Test + void shouldUseUnconfiguredPluginsWhenNotLocked() { + // Given + PluginFactory pluginFactory = new PluginFactory(); + pluginFactory.setLocked(false); + ZapXmlConfiguration conf = configWithPlugin(); + // When + pluginFactory.loadAllPlugin(conf); + // Then + List plugins = pluginFactory.getAllPlugin(); + assertThat(plugins.get(0).getId(), is(equalTo(1))); + assertThat(plugins.get(0).isEnabled(), is(equalTo(true))); + assertThat(plugins.get(1).getId(), is(equalTo(2))); + assertThat(plugins.get(1).isEnabled(), is(equalTo(true))); + } + + @Test + void shouldUseUnconfiguredPluginsWhenNotLockedThroughScanPolicy() throws Exception { + // Given + ZapXmlConfiguration conf = configWithPlugin(); + conf.setProperty("locked", false); + // When + ScanPolicy scanPolicy = new ScanPolicy(conf); + // Then + List plugins = scanPolicy.getPluginFactory().getAllPlugin(); + assertThat(plugins.get(0).getId(), is(equalTo(1))); + assertThat(plugins.get(0).isEnabled(), is(equalTo(true))); + assertThat(plugins.get(1).getId(), is(equalTo(2))); + assertThat(plugins.get(1).isEnabled(), is(equalTo(true))); + } + @Test void shouldOrderHighRiskAlertPluginsBeforeMedium() { // Given diff --git a/zap/src/test/java/org/zaproxy/zap/extension/ascan/ScanPolicyUnitTest.java b/zap/src/test/java/org/zaproxy/zap/extension/ascan/ScanPolicyUnitTest.java index 534142a0615..33ac2bcef9e 100644 --- a/zap/src/test/java/org/zaproxy/zap/extension/ascan/ScanPolicyUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/extension/ascan/ScanPolicyUnitTest.java @@ -25,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.parosproxy.paros.core.scanner.Plugin; import org.zaproxy.zap.WithConfigsTest; import org.zaproxy.zap.utils.ZapXmlConfiguration; @@ -120,4 +122,53 @@ void shouldUseMediumIfDefaultScannerStrengthFromConfigIsDefault() throws Excepti // Then assertThat(scanPolicy.getDefaultStrength(), is(equalTo(Plugin.AttackStrength.MEDIUM))); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldLoadLockedStateFromConfig(boolean locked) throws Exception { + // Given + ZapXmlConfiguration conf = new ZapXmlConfiguration(); + conf.setProperty("locked", locked); + // When + ScanPolicy scanPolicy = new ScanPolicy(conf); + // Then + assertThat(scanPolicy.isLocked(), is(equalTo(locked))); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldSetPluginFactoryLockedStateFromConfig(boolean locked) throws Exception { + // Given + ZapXmlConfiguration conf = new ZapXmlConfiguration(); + conf.setProperty("locked", locked); + // When + ScanPolicy scanPolicy = new ScanPolicy(conf); + // Then + assertThat(scanPolicy.getPluginFactory().isLocked(), is(equalTo(locked))); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldSetLockedState(boolean locked) { + // Given + ScanPolicy scanPolicy = new ScanPolicy(); + // When + scanPolicy.setLocked(locked); + // Then + assertThat(scanPolicy.isLocked(), is(equalTo(locked))); + assertThat(scanPolicy.getPluginFactory().isLocked(), is(equalTo(locked))); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldSaveLockedState(boolean locked) throws Exception { + // Given + ZapXmlConfiguration conf = new ZapXmlConfiguration(); + ScanPolicy scanPolicy = new ScanPolicy(); + scanPolicy.setLocked(locked); + // When + scanPolicy.saveTo(conf); + // Then + assertThat(conf.getBoolean("locked"), is(equalTo(locked))); + } } From f68ad4c9503169589b3ad3391398ae102815cdf9 Mon Sep 17 00:00:00 2001 From: Simon Bennetts Date: Mon, 17 Nov 2025 16:11:01 +0000 Subject: [PATCH 2/2] Add content-type to SiteMap added events Signed-off-by: Simon Bennetts --- .../org/parosproxy/paros/model/SiteMap.java | 41 +++++-- .../paros/model/SiteMapUnitTest.java | 110 ++++++++++++++++++ 2 files changed, 141 insertions(+), 10 deletions(-) diff --git a/zap/src/main/java/org/parosproxy/paros/model/SiteMap.java b/zap/src/main/java/org/parosproxy/paros/model/SiteMap.java index ca0f76e6c90..5783145a594 100644 --- a/zap/src/main/java/org/parosproxy/paros/model/SiteMap.java +++ b/zap/src/main/java/org/parosproxy/paros/model/SiteMap.java @@ -79,6 +79,7 @@ // ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging. // ZAP: 2023/01/10 Tidy up logger. // ZAP: 2024/01/19 Store clean node name when adding leaf node. +// ZAP: 2025/11/17 Add contentType to ADD events. package org.parosproxy.paros.model; import java.awt.EventQueue; @@ -530,7 +531,11 @@ private SiteNode findAndAddChild( hrefMap.put(result.getHistoryReference().getHistoryId(), result); applyFilter(newNode); - handleEvent(parent, result, EventType.ADD); + handleEvent( + parent, + result, + EventType.ADD, + getAddEventParameters(HistoryReference.TYPE_TEMPORARY, baseMsg)); } // ZAP: Cope with getSiteNode() returning null if (baseRef.getSiteNode() == null) { @@ -597,7 +602,8 @@ private SiteNode findAndAddLeaf( this.applyFilter(node); - handleEvent(parent, node, EventType.ADD); + handleEvent( + parent, node, EventType.ADD, getAddEventParameters(ref.getHistoryType(), msg)); } else if (hrefMap.get(ref.getHistoryId()) != node) { // Give preference to successful requests but update same statuses'. @@ -615,6 +621,16 @@ private SiteNode findAndAddLeaf( return node; } + private Map getAddEventParameters(int hrefType, HttpMessage msg) { + if (hrefType != HistoryReference.TYPE_TEMPORARY) { + String contentType = msg.getResponseHeader().getNormalisedContentTypeValue(); + if (contentType != null) { + return Map.of("contentType", contentType); + } + } + return Map.of(); + } + public HistoryReference createReference( SiteNode node, HistoryReference baseRef, HttpMessage base) throws HttpMalformedHeaderException, @@ -761,7 +777,7 @@ private void clearParentFilter(SiteNode parent) { public void removeNodeFromParent(MutableTreeNode node) { SiteNode parent = (SiteNode) node.getParent(); super.removeNodeFromParent(node); - handleEvent(parent, (SiteNode) node, EventType.REMOVE); + handleEvent(parent, (SiteNode) node, EventType.REMOVE, Map.of()); } /** @@ -774,18 +790,19 @@ public void removeNodeFromParent(MutableTreeNode node) { * @see EventType * @since 2.5.0 */ - private void handleEvent(SiteNode parent, SiteNode node, EventType eventType) { + private void handleEvent( + SiteNode parent, SiteNode node, EventType eventType, Map parameters) { switch (eventType) { case ADD: - publishEvent(SiteMapEventPublisher.SITE_NODE_ADDED_EVENT, node); + publishEvent(SiteMapEventPublisher.SITE_NODE_ADDED_EVENT, node, parameters); if (parent == getRoot()) { - publishEvent(SiteMapEventPublisher.SITE_ADDED_EVENT, node); + publishEvent(SiteMapEventPublisher.SITE_ADDED_EVENT, node, parameters); } break; case REMOVE: - publishEvent(SiteMapEventPublisher.SITE_NODE_REMOVED_EVENT, node); + publishEvent(SiteMapEventPublisher.SITE_NODE_REMOVED_EVENT, node, parameters); if (parent == getRoot()) { - publishEvent(SiteMapEventPublisher.SITE_REMOVED_EVENT, node); + publishEvent(SiteMapEventPublisher.SITE_REMOVED_EVENT, node, parameters); } } } @@ -797,11 +814,15 @@ private void handleEvent(SiteNode parent, SiteNode node, EventType eventType) { * @param node the node being acted upon * @since 2.5.0 */ - private static void publishEvent(String event, SiteNode node) { + private static void publishEvent(String event, SiteNode node, Map parameters) { ZAP.getEventBus() .publishSyncEvent( SiteMapEventPublisher.getPublisher(), - new Event(SiteMapEventPublisher.getPublisher(), event, new Target(node))); + new Event( + SiteMapEventPublisher.getPublisher(), + event, + new Target(node), + parameters)); } @Override diff --git a/zap/src/test/java/org/parosproxy/paros/model/SiteMapUnitTest.java b/zap/src/test/java/org/parosproxy/paros/model/SiteMapUnitTest.java index bd62c2e137e..9fe194cedd1 100644 --- a/zap/src/test/java/org/parosproxy/paros/model/SiteMapUnitTest.java +++ b/zap/src/test/java/org/parosproxy/paros/model/SiteMapUnitTest.java @@ -33,8 +33,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.TreeSet; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; @@ -51,6 +53,9 @@ import org.parosproxy.paros.network.HtmlParameter; import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.ZAP; +import org.zaproxy.zap.eventBus.Event; +import org.zaproxy.zap.eventBus.EventConsumer; import org.zaproxy.zap.extension.ascan.VariantFactory; import org.zaproxy.zap.model.StandardParameterParser; @@ -313,6 +318,100 @@ void shouldGetCleanNameForLeafNodeWithQueryAndFormParams() throws Exception { assertThat(leaf.getCleanNodeName(), is(equalTo("cat"))); } + @Test + void shouldPublishAddedEvents() throws Exception { + // Given + String uri = "http://example.com/"; + HistoryReference href = createHistoryReference(uri); + given(href.getHistoryType()).willReturn(HistoryReference.TYPE_ZAP_USER); + given(href.getHistoryId()).willReturn(100); + + TestEventConsumer testConsumer = new TestEventConsumer(); + ZAP.getEventBus() + .registerConsumer(testConsumer, SiteMapEventPublisher.class.getCanonicalName()); + + // When + siteMap.addPath(href); + // Then + assertThat(testConsumer.events.size(), is(equalTo(3))); + assertThat(testConsumer.events.get(0).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(0).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com"))); + assertThat(testConsumer.events.get(0).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(1).getEventType(), is(equalTo("site.added"))); + assertThat( + testConsumer.events.get(1).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com"))); + assertThat(testConsumer.events.get(1).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(2).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(2).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com/"))); + assertThat(testConsumer.events.get(2).getParameters().size(), is(equalTo(1))); + assertThat( + testConsumer.events.get(2).getParameters().containsKey("contentType"), + is(equalTo(true))); + assertThat( + testConsumer.events.get(2).getParameters().get("contentType"), + is(equalTo("text/html"))); + } + + @Test + void shouldOnlyAddContentTypeToLeafPublishEvents() throws Exception { + // Given + String uri = "http://example.com/a/b/c"; + HistoryReference href = createHistoryReference(uri); + given(href.getHistoryType()).willReturn(HistoryReference.TYPE_ZAP_USER); + given(href.getHistoryId()).willReturn(100); + + TestEventConsumer testConsumer = new TestEventConsumer(); + ZAP.getEventBus() + .registerConsumer(testConsumer, SiteMapEventPublisher.class.getCanonicalName()); + + // When + siteMap.addPath(href); + // Then + assertThat(testConsumer.events.size(), is(equalTo(5))); + assertThat(testConsumer.events.get(0).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(0).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com"))); + assertThat(testConsumer.events.get(0).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(1).getEventType(), is(equalTo("site.added"))); + assertThat( + testConsumer.events.get(1).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com"))); + assertThat(testConsumer.events.get(1).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(2).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(2).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com/a"))); + assertThat(testConsumer.events.get(2).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(3).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(3).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com/a/b"))); + assertThat(testConsumer.events.get(3).getParameters().size(), is(equalTo(0))); + + assertThat(testConsumer.events.get(4).getEventType(), is(equalTo("siteNode.added"))); + assertThat( + testConsumer.events.get(4).getTarget().getStartNode().getHierarchicNodeName(), + is(equalTo("http://example.com/a/b/c"))); + assertThat(testConsumer.events.get(4).getParameters().size(), is(equalTo(1))); + assertThat( + testConsumer.events.get(4).getParameters().containsKey("contentType"), + is(equalTo(true))); + assertThat( + testConsumer.events.get(4).getParameters().get("contentType"), + is(equalTo("text/html"))); + } + private void siteMapWithNodes(String... uris) { Arrays.stream(uris).forEach(uri -> siteMap.addPath(createHistoryReference(uri))); } @@ -328,6 +427,7 @@ private static HistoryReference createHistoryReference(String uri, String method try { HttpMessage httpMessage = new HttpMessage(requestUri); httpMessage.getRequestHeader().setMethod(method); + httpMessage.getResponseHeader().addHeader("content-type", "text/html"); given(historyReference.getHttpMessage()).willReturn(httpMessage); } catch (HttpMalformedHeaderException | DatabaseException e) { throw new RuntimeException(e); @@ -342,4 +442,14 @@ private static URI createUri(String uri) { throw new RuntimeException(e); } } + + private class TestEventConsumer implements EventConsumer { + + List events = new ArrayList<>(); + + @Override + public void eventReceived(Event event) { + events.add(event); + } + } }