Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
// 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.
// ZAP: 2026/09/22 From now on we will not be recording changes here as the files have changed so
// much.
package org.parosproxy.paros.core.scanner;

import java.util.ArrayList;
Expand All @@ -72,6 +74,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
Expand All @@ -86,6 +89,7 @@ public class PluginFactory {
private static final Logger LOGGER = LogManager.getLogger(PluginFactory.class);
private static List<AbstractPlugin> loadedPlugins = null;
private static Map<Integer, Plugin> mapLoadedPlugins;
private static final List<PluginLoaderListener> LOADER_LISTENERS = new CopyOnWriteArrayList<>();

private List<Plugin> listAllPlugin = new ArrayList<>();
private LinkedHashMap<Integer, Plugin> mapAllPlugin =
Expand Down Expand Up @@ -219,6 +223,7 @@ public static void loadedPlugin(AbstractPlugin plugin) {
getLoadedPlugins().add(plugin);
mapLoadedPlugins.put(plugin.getId(), plugin);
Collections.sort(loadedPlugins, riskComparator);
notifyPluginLoaded(plugin);
}
}

Expand All @@ -230,11 +235,77 @@ public static void unloadedPlugin(AbstractPlugin plugin) {
if (it.next() == plugin) {
it.remove();
mapLoadedPlugins.remove(plugin.getId());
notifyPluginUnloaded(plugin);
return;
}
}
}

/**
* Adds a listener notified whenever a plugin is loaded or unloaded via {@link
* #loadedPlugin(AbstractPlugin)} / {@link #unloadedPlugin(AbstractPlugin)}. This is not part of
* the public API.
*
* @param listener the listener to add.
* @since 2.18.0
*/
public static void addPluginLoaderListener(PluginLoaderListener listener) {
LOADER_LISTENERS.add(listener);
}

/**
* Removes a previously added {@link PluginLoaderListener}. This is not part of the public API.
*
* @param listener the listener to remove.
* @since 2.18.0
*/
public static void removePluginLoaderListener(PluginLoaderListener listener) {
LOADER_LISTENERS.remove(listener);
}

private static void notifyPluginLoaded(AbstractPlugin plugin) {
for (PluginLoaderListener listener : LOADER_LISTENERS) {
try {
listener.pluginLoaded(plugin);
} catch (Exception e) {
LOGGER.error("Failed to notify listener of loaded plugin {}", plugin.getId(), e);
}
}
}

private static void notifyPluginUnloaded(AbstractPlugin plugin) {
for (PluginLoaderListener listener : LOADER_LISTENERS) {
try {
listener.pluginUnloaded(plugin);
} catch (Exception e) {
LOGGER.error("Failed to notify listener of unloaded plugin {}", plugin.getId(), e);
}
}
}

/**
* Notified when a plugin is loaded or unloaded. This is not part of the public API.
*
* @see PluginFactory#addPluginLoaderListener(PluginLoaderListener)
* @since 2.18.0
*/
public interface PluginLoaderListener {

/**
* Called after a plugin has been loaded.
*
* @param plugin the plugin that was loaded.
*/
void pluginLoaded(AbstractPlugin plugin);

/**
* Called after a plugin has been unloaded.
*
* @param plugin the plugin that was unloaded.
*/
void pluginUnloaded(AbstractPlugin plugin);
}

// now order the list by the highest risk thrown, in descending order (to execute the more
// critical checks first)
private static final Comparator<AbstractPlugin> riskComparator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -60,6 +64,79 @@ void setUp() throws Exception {
PluginFactory.init(false);
}

@AfterEach
void tearDown() {
loaderListeners.forEach(PluginFactory::removePluginLoaderListener);
}

private final List<PluginFactory.PluginLoaderListener> loaderListeners = new ArrayList<>();

private PluginFactory.PluginLoaderListener addMockLoaderListener() {
PluginFactory.PluginLoaderListener listener = mock();
loaderListeners.add(listener);
PluginFactory.addPluginLoaderListener(listener);
return listener;
}

@Test
void shouldNotifyListenerWhenPluginLoaded() {
// Given
AbstractPlugin plugin = createAbstractPlugin();
PluginFactory.PluginLoaderListener listener = addMockLoaderListener();
// When
PluginFactory.loadedPlugin(plugin);
// Then
verify(listener).pluginLoaded(plugin);
verify(listener, never()).pluginUnloaded(any());
}

@Test
void shouldNotifyListenerWhenPluginUnloaded() {
// Given
AbstractPlugin plugin = createAbstractPlugin();
PluginFactory.loadedPlugin(plugin);
PluginFactory.PluginLoaderListener listener = addMockLoaderListener();
// When
PluginFactory.unloadedPlugin(plugin);
// Then
verify(listener).pluginUnloaded(plugin);
}

@Test
void shouldNotNotifyListenerWhenLoadingAnAlreadyLoadedPlugin() {
// Given
AbstractPlugin plugin = createAbstractPlugin();
PluginFactory.loadedPlugin(plugin);
PluginFactory.PluginLoaderListener listener = addMockLoaderListener();
// When
PluginFactory.loadedPlugin(plugin);
// Then
verify(listener, never()).pluginLoaded(any());
}

@Test
void shouldNotNotifyListenerWhenUnloadingANotYetLoadedPlugin() {
// Given
AbstractPlugin plugin = createAbstractPlugin();
PluginFactory.PluginLoaderListener listener = addMockLoaderListener();
// When
PluginFactory.unloadedPlugin(plugin);
// Then
verify(listener, never()).pluginUnloaded(any());
}

@Test
void shouldNotNotifyRemovedListener() {
// Given
AbstractPlugin plugin = createAbstractPlugin();
PluginFactory.PluginLoaderListener listener = addMockLoaderListener();
PluginFactory.removePluginLoaderListener(listener);
// When
PluginFactory.loadedPlugin(plugin);
// Then
verify(listener, never()).pluginLoaded(any());
}

@Test
void shouldNotHaveUndefinedPluginLoaded() {
// Given
Expand Down
Loading