org.zaproxy.zap.extension.spider.ExtensionSpider.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.spider.ExtensionSpider.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 * 
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 * 
 * 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.
 * 
 * Note that this extension and the other classes in this package are heavily 
 * based on the original Paros ExtensionSpider! 
 */

package org.zaproxy.zap.extension.spider;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

import org.apache.commons.httpclient.URI;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.control.Control.Mode;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.SessionChangedListener;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.extension.help.ExtensionHelp;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.model.ScanController;
import org.zaproxy.zap.model.StructuralNode;
import org.zaproxy.zap.model.StructuralSiteNode;
import org.zaproxy.zap.model.Target;
import org.zaproxy.zap.model.ValueGenerator;
import org.zaproxy.zap.model.DefaultValueGenerator;
import org.zaproxy.zap.spider.SpiderParam;
import org.zaproxy.zap.spider.filters.FetchFilter;
import org.zaproxy.zap.spider.filters.ParseFilter;
import org.zaproxy.zap.spider.filters.HttpPrefixFetchFilter;
import org.zaproxy.zap.spider.parser.SpiderParser;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.view.ZapMenuItem;

/**
 * The ExtensionSpider is the Extension that controls the Spider.
 */
public class ExtensionSpider extends ExtensionAdaptor
        implements SessionChangedListener, ScanController<SpiderScan> {

    private ValueGenerator generator = new DefaultValueGenerator();

    public static final int EXTENSION_ORDER = 30;

    /** The Constant logger. */
    private static final Logger log = Logger.getLogger(ExtensionSpider.class);

    /** The Constant defining the NAME of the extension. */
    public static final String NAME = "ExtensionSpider";

    /** The spider panel. */
    private SpiderPanel spiderPanel = null;

    SpiderDialog spiderDialog = null;

    private PopupMenuItemSpiderDialog popupMenuItemSpiderDialog;

    /** The options spider panel. */
    private OptionsSpiderPanel optionsSpiderPanel = null;

    /** The params for the spider. */
    private SpiderParam params = null;

    private List<SpiderParser> customParsers;
    private List<FetchFilter> customFetchFilters;
    private List<ParseFilter> customParseFilters;

    private SpiderAPI spiderApi;

    private SpiderScanController scanController = null;

    private Icon icon;

    /**
     * The list of excluded patterns of sites. Patterns are added here with the ExcludeFromSpider
     * Popup Menu.
     */
    private List<String> excludeList = Collections.emptyList();

    private ZapMenuItem menuItemCustomScan = null;

    /**
     * Instantiates a new spider extension.
     */
    public ExtensionSpider() {
        super(NAME);
        initialize();
    }

    /**
     * This method initializes this extension.
     */
    private void initialize() {
        this.setOrder(EXTENSION_ORDER);
        this.customParsers = new LinkedList<>();
        this.customFetchFilters = new LinkedList<>();
        this.customParseFilters = new LinkedList<>();
        this.scanController = new SpiderScanController(this);
    }

    public void setValueGenerator(ValueGenerator generator) {
        if (generator == null) {
            throw new IllegalArgumentException("Parameter generator must not be null.");
        }
        this.generator = generator;
    }

    public ValueGenerator getValueGenerator() {
        return generator;
    }

    @Override
    public String getUIName() {
        return Constant.messages.getString("spider.name");
    }

    @Override
    public void hook(ExtensionHook extensionHook) {
        super.hook(extensionHook);
        // Register for listeners
        extensionHook.addSessionListener(this);

        // Initialize views
        if (getView() != null) {
            extensionHook.getHookMenu().addToolsMenuItem(getMenuItemCustomScan());
            extensionHook.getHookView().addStatusPanel(getSpiderPanel());
            extensionHook.getHookView().addOptionPanel(getOptionsSpiderPanel());
            extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuItemSpiderDialog());
            ExtensionHelp.enableHelpKey(getSpiderPanel(), "ui.tabs.spider");
        }

        // Register the params
        extensionHook.addOptionsParamSet(getSpiderParam());

        // Register as an API implementor
        spiderApi = new SpiderAPI(this);
        spiderApi.addApiOptions(getSpiderParam());
        extensionHook.addApiImplementor(spiderApi);
    }

    private PopupMenuItemSpiderDialog getPopupMenuItemSpiderDialog() {
        if (popupMenuItemSpiderDialog == null) {
            popupMenuItemSpiderDialog = new PopupMenuItemSpiderDialog(this);
        }
        return popupMenuItemSpiderDialog;
    }

    @Override
    public List<String> getActiveActions() {
        List<SpiderScan> activeSpiders = scanController.getActiveScans();
        if (activeSpiders.isEmpty()) {
            return null;
        }

        String spiderActionPrefix = Constant.messages.getString("spider.activeActionPrefix");
        List<String> activeActions = new ArrayList<>(activeSpiders.size());
        for (SpiderScan activeSpider : activeSpiders) {
            activeActions.add(MessageFormat.format(spiderActionPrefix, activeSpider.getDisplayName()));
        }
        return activeActions;
    }

    /**
     * Gets the spider parameters (options).
     * 
     * @return the spider parameters
     */
    protected SpiderParam getSpiderParam() {
        if (params == null) {
            params = new SpiderParam();
        }
        return params;
    }

    /**
     * Gets the spider panel.
     * 
     * @return the spider panel
     */
    protected SpiderPanel getSpiderPanel() {
        if (spiderPanel == null) {
            spiderPanel = new SpiderPanel(this, getSpiderParam());
        }
        return spiderPanel;
    }

    @Override
    public void sessionAboutToChange(Session session) {
        // Shut all of the scans down and remove them
        this.scanController.reset();
        if (View.isInitialised()) {
            this.getSpiderPanel().reset();
            if (spiderDialog != null) {
                spiderDialog.reset();
            }
        }
    }

    @Override
    public void sessionChanged(final Session session) {
        if (EventQueue.isDispatchThread()) {
            sessionChangedEventHandler(session);
        } else {
            try {
                EventQueue.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        sessionChangedEventHandler(session);
                    }
                });
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * Session changed event handler.
     * 
     * @param session the session
     */
    private void sessionChangedEventHandler(Session session) {
        // Clear all scans
        if (View.isInitialised()) {
            this.getSpiderPanel().reset();
        }
        if (session == null) {
            // Closedown
            return;
        }
    }

    /**
     * Gets the options spider panel.
     * 
     * @return the options spider panel
     */
    private OptionsSpiderPanel getOptionsSpiderPanel() {
        if (optionsSpiderPanel == null) {
            optionsSpiderPanel = new OptionsSpiderPanel();
        }
        return optionsSpiderPanel;
    }

    /**
     * Sets the exclude list.
     * 
     * @param ignoredRegexs the new exclude list
     */
    public void setExcludeList(List<String> ignoredRegexs) {
        if (ignoredRegexs == null || ignoredRegexs.isEmpty()) {
            excludeList = Collections.emptyList();
            return;
        }

        this.excludeList = ignoredRegexs;
    }

    /**
     * Gets the exclude list.
     * 
     * @return the exclude list
     */
    public List<String> getExcludeList() {
        return excludeList;
    }

    @Override
    public String getAuthor() {
        return Constant.ZAP_TEAM;
    }

    @Override
    public String getDescription() {
        return Constant.messages.getString("spider.desc");
    }

    @Override
    public URL getURL() {
        try {
            return new URL(Constant.ZAP_HOMEPAGE);
        } catch (MalformedURLException e) {
            return null;
        }
    }

    @Override
    public void sessionScopeChanged(Session session) {
        if (View.isInitialised()) {
            this.getSpiderPanel().sessionScopeChanged(session);
        }
    }

    @Override
    public void sessionModeChanged(Mode mode) {
        if (View.isInitialised()) {
            this.getSpiderPanel().sessionModeChanged(mode);
            getMenuItemCustomScan().setEnabled(!Mode.safe.equals(mode));
        }
    }

    /**
     * Start scan node.
     * 
     * @param node the node
     */
    public void startScanNode(SiteNode node) {
        Target target = new Target(node);
        target.setRecurse(true);
        this.startScan(target, null, null);
    }

    /**
     * Start the scan of an URL (Node) from the POV of a User.
     * 
     * @param node the node
     */
    public void startScanNode(SiteNode node, User user) {
        Target target = new Target(node);
        target.setRecurse(true);
        this.startScan(target, user, null);
    }

    /**
     * Start scan all in scope.
     */
    public void startScanAllInScope() {
        Target target = new Target(true);
        target.setRecurse(true);
        this.startScan(target, null, null);
    }

    /**
     * Start scan.
     * 
     * @param startNode the start node
     */
    public void startScan(SiteNode startNode) {
        Target target = new Target(startNode);
        target.setRecurse(true);
        this.startScan(target, null, null);
    }

    /**
     * Start scan all in context, from the POV of an User.
     */
    public void startScanAllInContext(Context context, User user) {
        Target target = new Target(context);
        target.setRecurse(true);
        this.startScan(target, user, null);
    }

    @Override
    public void destroy() {
        // Shut all of the scans down
        this.stopAllScans();
        if (View.isInitialised()) {
            this.getSpiderPanel().reset();
        }
    }

    /**
     * Gets the custom parsers loaded.
     *
     * @return the custom parsers
     */
    public List<SpiderParser> getCustomParsers() {
        return customParsers;
    }

    /**
     * Gets the custom fetch filters loaded.
     *
     * @return the custom fetch filters
     */
    public List<FetchFilter> getCustomFetchFilters() {
        return customFetchFilters;
    }

    /**
     * Gets the custom parse filters loaded.
     *
     * @return the custom parse filters
     */
    public List<ParseFilter> getCustomParseFilters() {
        return customParseFilters;
    }

    /**
     * Adds a new custom Spider Parser. The parser is added at the beginning of the parsers list so
     * it will be processed before other already loaded parsers and before the default parsers.
     * <p/>
     * This method should be used to customize the Spider from any other extension of ZAP. The
     * parsers added will be loaded whenever starting any scan.
     * 
     * @param parser the parser
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @see #removeCustomParser(SpiderParser)
     */
    public void addCustomParser(SpiderParser parser) {
        validateParameterNonNull(parser, "parser");
        this.customParsers.add(parser);
    }

    private static void validateParameterNonNull(Object object, String name) {
        if (object == null) {
            throw new IllegalArgumentException("Parameter " + name + " must not be null.");
        }
    }

    /**
     * Removes the given spider parser.
     * <p>
     * Nothing happens if the given parser was not previously added.
     * 
     * @param parser the parser
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @since 2.6.0
     * @see #addCustomParser(SpiderParser)
     */
    public void removeCustomParser(SpiderParser parser) {
        validateParameterNonNull(parser, "parser");
        this.customParsers.remove(parser);
    }

    /**
     * Adds a custom fetch filter that would be used during the spidering.
     * <p/>
     * This method should be used to customize the Spider from any other extension of ZAP. The
     * filters added will be loaded whenever starting any scan.
     * 
     * @param filter the filter
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @see #removeCustomFetchFilter(FetchFilter)
     */
    public void addCustomFetchFilter(FetchFilter filter) {
        validateParameterNonNull(filter, "filter");
        this.customFetchFilters.add(filter);
    }

    /**
     * Removes the given fetch filter.
     * <p>
     * Nothing happens if the given filter was not previously added.
     * 
     * @param filter the filter
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @since 2.6.0
     * @see #addCustomFetchFilter(FetchFilter)
     */
    public void removeCustomFetchFilter(FetchFilter filter) {
        validateParameterNonNull(filter, "filter");
        this.customFetchFilters.remove(filter);
    }

    /**
     * Adds a custom parse filter that would be used during the spidering.
     * <p/>
     * This method should be used to customize the Spider from any other extension of ZAP. The
     * filters added will be loaded whenever starting any scan.
     * 
     * @param filter the filter
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @see #removeCustomParseFilter(ParseFilter)
     */
    public void addCustomParseFilter(ParseFilter filter) {
        validateParameterNonNull(filter, "filter");
        this.customParseFilters.add(filter);
    }

    /**
     * Removes the given parse filter.
     * <p>
     * Nothing happens if the given filter was not previously added.
     * 
     * @param filter the filter
     * @throws IllegalArgumentException if the given parameter is {@code null}.
     * @since 2.6.0
     * @see #addCustomParseFilter(ParseFilter)
     */
    public void removeCustomParseFilter(ParseFilter filter) {
        validateParameterNonNull(filter, "filter");
        this.customParseFilters.remove(filter);
    }

    /**
     * Starts a new spider scan using the given target and, optionally, spidering from the perspective of a user and with custom
     * configurations.
     * <p>
     * The spider scan will use the most appropriate display name created from the given target, user and custom configurations.
     *
     * @param target the target that will be spidered
     * @param user the user that will be used to spider, might be {@code null}
     * @param customConfigurations other custom configurations for the spider, might be {@code null}
     * @return the ID of the spider scan
     * @since 2.5.0
     * @see #startScan(String, Target, User, Object[])
     * @throws IllegalStateException if the target or custom configurations are not allowed in the current
     *             {@link org.parosproxy.paros.control.Control.Mode mode}.
     */
    public int startScan(Target target, User user, Object[] customConfigurations) {
        return startScan(createDisplayName(target, customConfigurations), target, user, customConfigurations);
    }

    /**
     * Creates the display name for the given target and, optionally, the given custom configurations.
     *
     * @param target the target that will be spidered
     * @param customConfigurations other custom configurations for the spider, might be {@code null}
     * @return a {@code String} containing the display name, never {@code null}
     */
    private String createDisplayName(Target target, Object[] customConfigurations) {
        HttpPrefixFetchFilter subtreeFecthFilter = getUriPrefixFecthFilter(customConfigurations);
        if (subtreeFecthFilter != null) {
            return abbreviateDisplayName(subtreeFecthFilter.getNormalisedPrefix());
        }

        if (target.getContext() != null) {
            return Constant.messages.getString("context.prefixName", target.getContext().getName());
        } else if (target.isInScopeOnly()) {
            return Constant.messages.getString("target.allInScope");
        } else if (target.getStartNode() == null) {
            if (customConfigurations != null) {
                for (Object customConfiguration : customConfigurations) {
                    if (customConfiguration instanceof URI) {
                        return abbreviateDisplayName(((URI) customConfiguration).toString());
                    }
                }
            }
            return Constant.messages.getString("target.empty");
        }
        return abbreviateDisplayName(target.getStartNode().getHierarchicNodeName(false));
    }

    /**
     * Gets the {@code HttpPrefixFetchFilter} from the given {@code customConfigurations}.
     *
     * @param customConfigurations the custom configurations of the spider
     * @return the {@code HttpPrefixFetchFilter} found, {@code null} otherwise.
     */
    private HttpPrefixFetchFilter getUriPrefixFecthFilter(Object[] customConfigurations) {
        if (customConfigurations != null) {
            for (Object customConfiguration : customConfigurations) {
                if (customConfiguration instanceof HttpPrefixFetchFilter) {
                    return (HttpPrefixFetchFilter) customConfiguration;
                }
            }
        }
        return null;
    }

    /**
     * Abbreviates (the middle of) the given display name if greater than 30 characters.
     *
     * @param displayName the display name that might be abbreviated
     * @return the, possibly, abbreviated display name
     */
    private static String abbreviateDisplayName(String displayName) {
        return StringUtils.abbreviateMiddle(displayName, "..", 30);
    }

    /**
     * Starts a new spider scan, with the given display name, using the given target and, optionally, spidering from the
     * perspective of a user and with custom configurations.
     * <p>
     * <strong>Note:</strong> The preferred method to start the scan is with {@link #startScan(Target, User, Object[])}, unless
     * a custom display name is really needed.
     * 
     * @param target the target that will be spidered
     * @param user the user that will be used to spider, might be {@code null}
     * @param customConfigurations other custom configurations for the spider, might be {@code null}
     * @return the ID of the spider scan
     * @throws IllegalStateException if the target or custom configurations are not allowed in the current
     *             {@link org.parosproxy.paros.control.Control.Mode mode}.
     */
    @SuppressWarnings({ "fallthrough" })
    @Override
    public int startScan(String displayName, Target target, User user, Object[] customConfigurations) {
        switch (Control.getSingleton().getMode()) {
        case safe:
            throw new IllegalStateException("Scans are not allowed in Safe mode");
        case protect:
            String uri = getTargetUriOutOfScope(target, customConfigurations);
            if (uri != null) {
                throw new IllegalStateException(
                        "Scans are not allowed on targets not in scope when in Protected mode: " + uri);
            }
            //$FALL-THROUGH$
        case standard:
        case attack:
            // No problem
            break;
        }

        int id = this.scanController.startScan(displayName, target, user, customConfigurations);
        if (View.isInitialised()) {
            addScanToUi(this.scanController.getScan(id));
        }
        return id;
    }

    private void addScanToUi(final SpiderScan scan) {
        if (!EventQueue.isDispatchThread()) {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    addScanToUi(scan);
                }
            });
            return;
        }

        this.getSpiderPanel().scannerStarted(scan);
        scan.setListener(getSpiderPanel()); // So the UI gets updated
        this.getSpiderPanel().switchView(scan);
        this.getSpiderPanel().setTabFocus();
    }

    /**
     * Returns the first URI that is out of scope in the given {@code target}.
     *
     * @param target the target that will be checked
     * @return a {@code String} with the first URI out of scope, {@code null} if none found
     * @since 2.5.0
     * @see Session#isInScope(String)
     */
    protected String getTargetUriOutOfScope(Target target) {
        return getTargetUriOutOfScope(target, null);
    }

    /**
     * Returns the first URI that is out of scope in the given {@code target} or {@code contextSpecificObjects}.
     *
     * @param target the target that will be checked
     * @param contextSpecificObjects other {@code Objects} used to enhance the target
     * @return a {@code String} with the first URI out of scope, {@code null} if none found
     * @since 2.5.0
     * @see Session#isInScope(String)
     */
    protected String getTargetUriOutOfScope(Target target, Object[] contextSpecificObjects) {
        List<StructuralNode> nodes = target.getStartNodes();
        if (nodes != null) {
            for (StructuralNode node : nodes) {
                if (node == null) {
                    continue;
                }
                if (node instanceof StructuralSiteNode) {
                    SiteNode siteNode = ((StructuralSiteNode) node).getSiteNode();
                    if (!siteNode.isIncludedInScope()) {
                        return node.getURI().toString();
                    }
                } else {
                    String uri = node.getURI().toString();
                    if (!isTargetUriInScope(uri)) {
                        return uri;
                    }
                }
            }
        }
        if (contextSpecificObjects != null) {
            for (Object obj : contextSpecificObjects) {
                if (obj instanceof URI) {
                    String uri = ((URI) obj).toString();
                    if (!isTargetUriInScope(uri)) {
                        return uri;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Tells whether or not the given {@code uri} is in scope.
     *
     * @param uri the uri that will be checked
     * @return {@code true} if the {@code uri} is in scope, {@code false} otherwise
     * @since 2.5.0
     * @see Session#isInScope(String)
     */
    protected boolean isTargetUriInScope(String uri) {
        if (uri == null) {
            return false;
        }
        return getModel().getSession().isInScope(uri);
    }

    @Override
    public List<SpiderScan> getAllScans() {
        return this.scanController.getAllScans();
    }

    @Override
    public List<SpiderScan> getActiveScans() {
        return this.scanController.getActiveScans();
    }

    @Override
    public SpiderScan getScan(int id) {
        return this.scanController.getScan(id);
    }

    @Override
    public void stopScan(int id) {
        this.scanController.stopScan(id);
    }

    @Override
    public void stopAllScans() {
        this.scanController.stopAllScans();
    }

    @Override
    public void pauseScan(int id) {
        this.scanController.pauseScan(id);
        if (View.isInitialised()) {
            // Update the UI in case this was initiated from the API
            this.getSpiderPanel().updateScannerUI();
        }
    }

    @Override
    public void pauseAllScans() {
        this.scanController.pauseAllScans();
        if (View.isInitialised()) {
            // Update the UI in case this was initiated from the API
            this.getSpiderPanel().updateScannerUI();
        }
    }

    @Override
    public void resumeScan(int id) {
        this.scanController.resumeScan(id);
        if (View.isInitialised()) {
            // Update the UI in case this was initiated from the API
            this.getSpiderPanel().updateScannerUI();
        }
    }

    @Override
    public void resumeAllScans() {
        this.scanController.resumeAllScans();
        if (View.isInitialised()) {
            // Update the UI in case this was initiated from the API
            this.getSpiderPanel().updateScannerUI();
        }
    }

    @Override
    public SpiderScan removeScan(int id) {
        return this.scanController.removeScan(id);
    }

    @Override
    public int removeAllScans() {
        return this.scanController.removeAllScans();
    }

    @Override
    public int removeFinishedScans() {
        return this.scanController.removeFinishedScans();
    }

    @Override
    public SpiderScan getLastScan() {
        return this.scanController.getLastScan();
    }

    private ZapMenuItem getMenuItemCustomScan() {
        if (menuItemCustomScan == null) {
            menuItemCustomScan = new ZapMenuItem("menu.tools.spider", KeyStroke.getKeyStroke(KeyEvent.VK_S,
                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | KeyEvent.ALT_DOWN_MASK, false));
            menuItemCustomScan.setEnabled(Control.getSingleton().getMode() != Mode.safe);

            menuItemCustomScan.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    showSpiderDialog(null);
                }
            });

        }

        return menuItemCustomScan;
    }

    public void showSpiderDialog(SiteNode node) {
        if (spiderDialog == null) {
            spiderDialog = new SpiderDialog(this, View.getSingleton().getMainFrame(), new Dimension(700, 430));
        }
        if (spiderDialog.isVisible()) {
            // Its behind you! Actually not needed no the window is alwaysOnTop, but keeping in case we change that ;)
            spiderDialog.toFront();
            return;
        }
        if (node != null) {
            spiderDialog.init(new Target(node));
        } else {
            // Keep the previous target
            spiderDialog.init(null);
        }
        spiderDialog.setVisible(true);
    }

    @Override
    public boolean supportsLowMemory() {
        return true;
    }

    /**
     * No database tables used, so all supported
     */
    @Override
    public boolean supportsDb(String type) {
        return true;
    }

    /**
     * Gets the icon for spider related functionality.
     *
     * @return the icon
     */
    public Icon getIcon() {
        if (icon == null) {
            icon = new ImageIcon(ExtensionSpider.class.getResource("/resource/icon/16/spider.png"));
        }
        return icon;
    }
}