/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2013 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.zap.extension.spider;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import org.apache.commons.httpclient.URI;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.extension.users.ExtensionUserManagement;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.model.StructuralNode;
import org.zaproxy.zap.model.StructuralSiteNode;
import org.zaproxy.zap.model.Target;
import org.zaproxy.zap.spider.SpiderParam;
import org.zaproxy.zap.spider.filters.HttpPrefixFetchFilter;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.view.StandardFieldsDialog;
public class SpiderDialog extends StandardFieldsDialog {
private static final String FIELD_START = "spider.custom.label.start";
private static final String FIELD_CONTEXT = "spider.custom.label.context";
private static final String FIELD_USER = "spider.custom.label.user";
private static final String FIELD_RECURSE = "spider.custom.label.recurse";
private static final String FIELD_SUBTREE_ONLY = "spider.custom.label.spiderSubtreeOnly";
private static final String FIELD_ADVANCED = "spider.custom.label.adv";
private static final String FIELD_MAX_DEPTH = "spider.custom.label.maxDepth";
private static final String FIELD_MAX_CHILDREN = "spider.custom.label.maxChildren";
private static final String FIELD_MAX_DURATION = "spider.custom.label.maxDuration";
private static final String FIELD_SEND_REFERER = "spider.custom.label.sendReferer";
private static final String FIELD_PROCESS_FORMS = "spider.custom.label.processForms";
private static final String FIELD_POST_FORMS = "spider.custom.label.postForms";
private static final String FIELD_PARSE_COMMENTS = "spider.custom.label.parseComments";
private static final String FIELD_PARSE_ROBOTS = "spider.custom.label.parseRobots";
private static final String FIELD_PARSE_SITEMAP = "spider.custom.label.sitemap";
private static final String FIELD_PARSE_SVN = "spider.custom.label.parseSvn";
private static final String FIELD_PARSE_GIT = "spider.custom.label.parseGit";
private static final String FIELD_HANDLE_ODATA = "spider.custom.label.handleOdata";
private static Logger logger = Logger.getLogger(SpiderDialog.class);
private static final long serialVersionUID = 1L;
private JButton[] extraButtons = null;
private ExtensionSpider extension = null;
private SpiderParam spiderParam = null;
/**
* Flag that holds the previous checked state of the "Subtree Only" checkbox.
* <p>
* Used to restore the previous checked state between dialogue invocations.
*
* @see #FIELD_SUBTREE_ONLY
*/
private boolean subtreeOnlyPreviousCheckedState;
private ExtensionUserManagement extUserMgmt = (ExtensionUserManagement) Control.getSingleton().getExtensionLoader()
.getExtension(ExtensionUserManagement.NAME);
private Target target = null;
public SpiderDialog(ExtensionSpider ext, Frame owner, Dimension dim) {
super(owner, "spider.custom.title", dim, new String[]{
"spider.custom.tab.scope",
"spider.custom.tab.adv"
});
this.extension = ext;
// The first time init to the default options set, after that keep own copies
reset(false);
}
public void init(Target target) {
if (target != null) {
// If one isnt specified then leave the previously selected one
this.target = target;
}
logger.debug("init " + this.target);
this.removeAllFields();
this.addTargetSelectField(0, FIELD_START, this.target, true, false);
this.addComboField(0, FIELD_CONTEXT, new String[] {}, "");
this.addComboField(0, FIELD_USER, new String[] {}, "");
this.addCheckBoxField(0, FIELD_RECURSE, true);
this.addCheckBoxField(0, FIELD_SUBTREE_ONLY, subtreeOnlyPreviousCheckedState);
// This option is always read from the 'global' options
this.addCheckBoxField(0, FIELD_ADVANCED, getSpiderParam().isShowAdvancedDialog());
this.addPadding(0);
// Advanced options
this.addNumberField(1, FIELD_MAX_DEPTH, 1, 19, getSpiderParam().getMaxDepth());
this.addNumberField(1, FIELD_MAX_CHILDREN, 0, Integer.MAX_VALUE, getSpiderParam().getMaxChildren());
this.addNumberField(1, FIELD_MAX_DURATION, 0, Integer.MAX_VALUE, getSpiderParam().getMaxDuration());
this.addCheckBoxField(1, FIELD_SEND_REFERER, getSpiderParam().isSendRefererHeader());
this.addCheckBoxField(1, FIELD_PROCESS_FORMS, getSpiderParam().isProcessForm());
this.addCheckBoxField(1, FIELD_POST_FORMS, getSpiderParam().isPostForm());
this.addCheckBoxField(1, FIELD_PARSE_COMMENTS, getSpiderParam().isParseComments());
this.addCheckBoxField(1, FIELD_PARSE_ROBOTS, getSpiderParam().isParseRobotsTxt());
this.addCheckBoxField(1, FIELD_PARSE_SITEMAP, getSpiderParam().isParseSitemapXml());
this.addCheckBoxField(1, FIELD_PARSE_SVN, getSpiderParam().isParseSVNEntries());
this.addCheckBoxField(1, FIELD_PARSE_GIT, getSpiderParam().isParseGit());
this.addCheckBoxField(1, FIELD_HANDLE_ODATA, getSpiderParam().isHandleODataParametersVisited());
this.addPadding(1);
if (! getBoolValue(FIELD_PROCESS_FORMS)) {
setFieldValue(FIELD_POST_FORMS, false);
getField(FIELD_POST_FORMS).setEnabled(false);
}
this.addFieldListener(FIELD_CONTEXT, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setUsers();
}
});
this.addFieldListener(FIELD_PROCESS_FORMS, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (getBoolValue(FIELD_PROCESS_FORMS)) {
getField(FIELD_POST_FORMS).setEnabled(true);
} else {
setFieldValue(FIELD_POST_FORMS, false);
getField(FIELD_POST_FORMS).setEnabled(false);
}
}
});
this.addFieldListener(FIELD_ADVANCED, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setAdvancedTabs(getBoolValue(FIELD_ADVANCED));
}
});
if (target != null) {
// Set up the fields if a node has been specified, otherwise leave as previously set
this.targetSelected(FIELD_START, this.target);
this.setUsers();
}
if ( ! extension.getSpiderParam().isShowAdvancedDialog()) {
// Remove all but the first tab
this.setAdvancedTabs(false);
}
this.pack();
}
private SpiderParam getSpiderParam() {
if (spiderParam == null) {
// First time in clone the global options, after that keep the last ones the user set
spiderParam = (SpiderParam) extension.getSpiderParam().clone();
}
return spiderParam;
}
private void setAdvancedTabs(boolean visible) {
// Show/hide all except from the first tab
this.setTabsVisible (new String[] {
"spider.custom.tab.adv"
}, visible);
}
@Override
public String getHelpIndex() {
return "ui.dialogs.spider";
}
@Override
public void targetSelected(String field, Target node) {
List<String> ctxNames = new ArrayList<String>();
if (node != null) {
// The user has selected a new node
this.target = node;
if (node.getStartNode() != null) {
Session session = Model.getSingleton().getSession();
List<Context> contexts = session.getContextsForNode(node.getStartNode());
for (Context context : contexts) {
ctxNames.add(context.getName());
}
} else if (node.getContext() != null) {
ctxNames.add(node.getContext().getName());
}
}
this.setComboFields(FIELD_CONTEXT, ctxNames, "");
this.getField(FIELD_CONTEXT).setEnabled(ctxNames.size() > 0);
}
private Context getSelectedContext() {
String ctxName = this.getStringValue(FIELD_CONTEXT);
if (this.extUserMgmt != null && ! this.isEmptyField(FIELD_CONTEXT)) {
Session session = Model.getSingleton().getSession();
return session.getContext(ctxName);
}
return null;
}
private User getSelectedUser() {
Context context = this.getSelectedContext();
if (context != null) {
String userName = this.getStringValue(FIELD_USER);
List<User> users = this.extUserMgmt.getContextUserAuthManager(context.getIndex()).getUsers();
for (User user : users) {
if (userName.equals(user.getName())) {
return user;
}
}
}
return null;
}
private void setUsers() {
Context context = this.getSelectedContext();
List<String> userNames = new ArrayList<String>();
if (context != null) {
List<User> users = this.extUserMgmt.getContextUserAuthManager(context.getIndex()).getUsers();
userNames.add(""); // The default should always be 'not specified'
for (User user : users) {
userNames.add(user.getName());
}
}
this.setComboFields(FIELD_USER, userNames, "");
this.getField(FIELD_USER).setEnabled(userNames.size() > 1); // Theres always 1..
}
private void reset(boolean refreshUi) {
// Reset to the global options
spiderParam = null;
subtreeOnlyPreviousCheckedState = false;
if (refreshUi) {
init(target);
repaint();
}
}
@Override
public String getSaveButtonText() {
return Constant.messages.getString("spider.custom.button.scan");
}
@Override
public JButton[] getExtraButtons() {
if (extraButtons == null) {
JButton resetButton = new JButton(Constant.messages.getString("spider.custom.button.reset"));
resetButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
reset(true);
}
});
extraButtons = new JButton[]{resetButton};
}
return extraButtons;
}
@Override
public void save() {
List<Object> contextSpecificObjects = new ArrayList<>();
URI startUri = null;
try {
// Always include the startUri, this has the side effect
// of handling URLs that have not been accessed
startUri = new URI(this.getStringValue(FIELD_START), true);
} catch (Exception e1) {
// Ignore - will have been checked in validateParams
}
if (this.getBoolValue(FIELD_ADVANCED)) {
// Set the advanced options
spiderParam.setMaxDepth(this.getIntValue(FIELD_MAX_DEPTH));
spiderParam.setMaxDuration(this.getIntValue(FIELD_MAX_DURATION));
spiderParam.setMaxChildren(this.getIntValue(FIELD_MAX_CHILDREN));
spiderParam.setSendRefererHeader(this.getBoolValue(FIELD_SEND_REFERER));
spiderParam.setProcessForm(this.getBoolValue(FIELD_PROCESS_FORMS));
spiderParam.setPostForm(this.getBoolValue(FIELD_POST_FORMS));
spiderParam.setParseComments(this.getBoolValue(FIELD_PARSE_COMMENTS));
spiderParam.setParseRobotsTxt(this.getBoolValue(FIELD_PARSE_ROBOTS));
spiderParam.setParseSitemapXml(this.getBoolValue(FIELD_PARSE_SITEMAP));
spiderParam.setParseSVNEntries(this.getBoolValue(FIELD_PARSE_SVN));
spiderParam.setParseGit(this.getBoolValue(FIELD_PARSE_GIT));
spiderParam.setHandleODataParametersVisited(this.getBoolValue(FIELD_HANDLE_ODATA));
spiderParam.setThreadCount(extension.getSpiderParam().getThreadCount());
contextSpecificObjects.add(spiderParam);
}
if (startUri != null) {
contextSpecificObjects.add(startUri);
if (getBoolValue(FIELD_SUBTREE_ONLY)) {
contextSpecificObjects.add(new HttpPrefixFetchFilter(startUri));
}
}
if (target == null || ! this.getStringValue(FIELD_START).equals(getTargetText(target))) {
// Clear the target as it doesnt match the value entered manually
target = new Target((StructuralNode)null);
}
// Save the adv option permanently for next time
extension.getSpiderParam().setShowAdvancedDialog(this.getBoolValue(FIELD_ADVANCED));
target.setRecurse(this.getBoolValue(FIELD_RECURSE));
if (target.getContext() == null && getSelectedContext() != null) {
target.setContext(getSelectedContext());
}
subtreeOnlyPreviousCheckedState = getBoolValue(FIELD_SUBTREE_ONLY);
this.extension.startScan(
target,
getSelectedUser(),
contextSpecificObjects.toArray());
}
@Override
public String validateFields() {
if (Control.Mode.safe == Control.getSingleton().getMode()) {
// The dialogue shouldn't be shown when in safe mode but if it is warn.
return Constant.messages.getString("spider.custom.notSafe.error");
}
if (this.isEmptyField(FIELD_START)) {
return Constant.messages.getString("spider.custom.nostart.error");
}
boolean noStartUri = true;
if (!getStringValue(FIELD_START).equals(getTargetText(target))) {
String url = this.getStringValue(FIELD_START);
try {
// Need both constructors as they catch slightly different issues ;)
new URI(url, true);
new URL(url);
} catch (Exception e) {
return Constant.messages.getString("spider.custom.nostart.error");
}
if (Control.getSingleton().getMode() == Control.Mode.protect) {
if (!extension.isTargetUriInScope(url)) {
return Constant.messages.getString("spider.custom.targetNotInScope.error", url);
}
}
noStartUri = false;
}
if (this.target != null) {
if (!this.target.isValid()) {
return Constant.messages.getString("spider.custom.nostart.error");
}
if (Control.getSingleton().getMode() == Control.Mode.protect) {
String uri = extension.getTargetUriOutOfScope(target);
if (uri != null) {
return Constant.messages.getString("spider.custom.targetNotInScope.error", uri);
}
}
List<StructuralNode> nodes = target.getStartNodes();
if (nodes != null) {
for (StructuralNode node : nodes) {
if (node instanceof StructuralSiteNode) {
noStartUri = false;
break;
}
}
}
}
if (getBoolValue(FIELD_SUBTREE_ONLY) && noStartUri) {
return Constant.messages.getString("spider.custom.noStartSubtreeOnly.error");
}
return null;
}
/**
* Resets the spider dialogue to its default state.
*
* @since 2.5.0
*/
void reset() {
target = null;
reset(true);
}
}