/* * 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. */ package org.zaproxy.zap.extension.spider; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.httpclient.URI; import org.apache.log4j.Logger; import org.zaproxy.zap.model.ScanController; import org.zaproxy.zap.model.Target; import org.zaproxy.zap.spider.SpiderParam; import org.zaproxy.zap.spider.filters.FetchFilter; import org.zaproxy.zap.spider.filters.MaxChildrenFetchFilter; import org.zaproxy.zap.spider.filters.MaxChildrenParseFilter; import org.zaproxy.zap.spider.filters.ParseFilter; import org.zaproxy.zap.spider.parser.SpiderParser; import org.zaproxy.zap.users.User; public class SpiderScanController implements ScanController<SpiderScan> { private static final Logger log = Logger.getLogger(SpiderScanController.class); private ExtensionSpider extension; /** * The {@code Lock} for exclusive access of instance variables related to multiple active scans. * * @see #spiderScanMap * @see #scanIdCounter */ private final Lock spiderScansLock; /** * The counter used to give an unique ID to active scans. * <p> * <strong>Note:</strong> All accesses (both write and read) should be done while holding the {@code Lock} * {@code spiderScansLock}. * </p> * * @see #spiderScansLock * @see #startScan(String, Target, User, Object[]) */ private int scanIdCounter; /** * A map that contains all {@code SpiderScan}s created (and not yet removed). Used to control (i.e. pause/resume and * stop) the multiple active scans and get its results. The instance variable is never {@code null}. The map key is the ID * of the scan. * <p> * <strong>Note:</strong> All accesses (both write and read) should be done while holding the {@code Lock} * {@code spiderScansLock}. * </p> * * @see #spiderScansLock * @see #startScan(String, Target, User, Object[]) * @see #scanIdCounter */ private Map<Integer, SpiderScan> spiderScanMap; /** * An ordered list of all of the {@code SpiderScan}s created (and not yet removed). Used to get provide the 'last' * scan for client using the 'old' API that didnt support concurrent scans. */ private List<SpiderScan> spiderScanList; public SpiderScanController (ExtensionSpider extension) { this.spiderScansLock = new ReentrantLock(); this.extension = extension; this.spiderScanMap = new HashMap<>(); this.spiderScanList = new ArrayList<SpiderScan>(); } @Override public int startScan(String name, Target target, User user, Object[] contextSpecificObjects) { spiderScansLock.lock(); try { int id = this.scanIdCounter++; SpiderParam spiderParams = extension.getSpiderParam(); List<SpiderParser> customSpiderParsers = new ArrayList<SpiderParser>(); List<FetchFilter> customFetchFilters = new ArrayList<FetchFilter>(); List<ParseFilter> customParseFilters = new ArrayList<ParseFilter>(); URI startUri = null; if (contextSpecificObjects != null) { for (Object obj : contextSpecificObjects) { if (obj instanceof SpiderParam) { log.debug("Setting custom spider params"); spiderParams = (SpiderParam) obj; } else if (obj instanceof SpiderParser) { customSpiderParsers.add((SpiderParser)obj); } else if (obj instanceof FetchFilter) { customFetchFilters.add((FetchFilter)obj); } else if (obj instanceof ParseFilter) { customParseFilters.add((ParseFilter)obj); } else if (obj instanceof URI) { startUri = (URI) obj; } else { log.error("Unexpected contextSpecificObject: " + obj.getClass().getCanonicalName()); } } } if (spiderParams.getMaxChildren() > 0) { // Add the filters to filter on maximum number of children MaxChildrenFetchFilter maxChildrenFetchFilter = new MaxChildrenFetchFilter(); maxChildrenFetchFilter.setMaxChildren(spiderParams.getMaxChildren()); maxChildrenFetchFilter.setModel(extension.getModel()); MaxChildrenParseFilter maxChildrenParseFilter = new MaxChildrenParseFilter(); maxChildrenParseFilter.setMaxChildren(spiderParams.getMaxChildren()); maxChildrenParseFilter.setModel(extension.getModel()); customFetchFilters.add(maxChildrenFetchFilter); customParseFilters.add(maxChildrenParseFilter); } SpiderScan scan = new SpiderScan(extension, spiderParams, target, startUri, user, id, name); scan.setCustomSpiderParsers(customSpiderParsers); scan.setCustomFetchFilters(customFetchFilters); scan.setCustomParseFilters(customParseFilters); this.spiderScanMap.put(id, scan); this.spiderScanList.add(scan); scan.start(); return id; } finally { spiderScansLock.unlock(); } } @Override public SpiderScan getScan(int id) { return this.spiderScanMap.get(id); } @Override public SpiderScan getLastScan() { spiderScansLock.lock(); try { if (spiderScanList.size() == 0) { return null; } return spiderScanList.get(spiderScanList.size()-1); } finally { spiderScansLock.unlock(); } } @Override public List<SpiderScan> getAllScans() { List<SpiderScan> list = new ArrayList<SpiderScan>(); spiderScansLock.lock(); try { for (SpiderScan scan : spiderScanList) { list.add(scan); } return list; } finally { spiderScansLock.unlock(); } } @Override public List<SpiderScan> getActiveScans() { List<SpiderScan> list = new ArrayList<SpiderScan>(); spiderScansLock.lock(); try { for (SpiderScan scan : spiderScanList) { if (!scan.isStopped()) { list.add(scan); } } return list; } finally { spiderScansLock.unlock(); } } @Override public SpiderScan removeScan(int id) { spiderScansLock.lock(); try { SpiderScan ascan = this.spiderScanMap.get(id); if (! spiderScanMap.containsKey(id)) { //throw new IllegalArgumentException("Unknown id " + id); return null; } ascan.stopScan(); ascan.clear(); spiderScanMap.remove(id); spiderScanList.remove(ascan); return ascan; } finally { spiderScansLock.unlock(); } } public int getTotalNumberScans() { return spiderScanMap.size(); } @Override public void stopAllScans() { spiderScansLock.lock(); try { for (SpiderScan scan : spiderScanMap.values()) { scan.stopScan(); } } finally { spiderScansLock.unlock(); } } @Override public void pauseAllScans() { spiderScansLock.lock(); try { for (SpiderScan scan : spiderScanMap.values()) { scan.pauseScan(); } } finally { spiderScansLock.unlock(); } } @Override public void resumeAllScans() { spiderScansLock.lock(); try { for (SpiderScan scan : spiderScanMap.values()) { scan.resumeScan(); } } finally { spiderScansLock.unlock(); } } @Override public int removeAllScans() { spiderScansLock.lock(); try { int count = 0; for (Iterator<SpiderScan> it = spiderScanMap.values().iterator(); it.hasNext();) { SpiderScan ascan = it.next(); ascan.stopScan(); ascan.clear(); it.remove(); spiderScanList.remove(ascan); count++; } return count; } finally { spiderScansLock.unlock(); } } @Override public int removeFinishedScans() { spiderScansLock.lock(); try { int count = 0; for (Iterator<SpiderScan> it = spiderScanMap.values().iterator(); it.hasNext();) { SpiderScan scan = it.next(); if (scan.isStopped()) { scan.stopScan(); scan.clear(); it.remove(); spiderScanList.remove(scan); count ++; } } return count; } finally { spiderScansLock.unlock(); } } @Override public void stopScan(int id) { spiderScansLock.lock(); try { if (this.spiderScanMap.containsKey(id)) { this.spiderScanMap.get(id).stopScan(); } } finally { spiderScansLock.unlock(); } } @Override public void pauseScan(int id) { spiderScansLock.lock(); try { if (this.spiderScanMap.containsKey(id)) { this.spiderScanMap.get(id).pauseScan(); } } finally { spiderScansLock.unlock(); } } @Override public void resumeScan(int id) { spiderScansLock.lock(); try { if (this.spiderScanMap.containsKey(id)) { this.spiderScanMap.get(id).resumeScan(); } } finally { spiderScansLock.unlock(); } } public void reset() { this.removeAllScans(); spiderScansLock.lock(); try { this.scanIdCounter = 0; } finally { spiderScansLock.unlock(); } } }