/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2010 psiinon@gmail.com
*
* 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.search;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.extension.search.ExtensionSearch.Type;
public class SearchThread extends Thread {
private static final String THREAD_NAME = "ZAP-SearchThread";
private String filter;
private Type reqType;
private SearchListenner searchListenner;
private boolean stopSearch = false;
private boolean inverse = false;
private boolean searchJustInScope = false;
private String baseUrl;
private PaginationConstraintsChecker pcc;
private Map<String, HttpSearcher> searchers;
private String customSearcherName;
private boolean searchAllOccurrences;
private static Logger log = Logger.getLogger(SearchThread.class);
public SearchThread(String filter, Type reqType, SearchListenner searchListenner, boolean inverse, boolean searchJustInScope,
String baseUrl, int start, int count) {
this(filter, reqType, searchListenner, inverse, searchJustInScope, baseUrl, start, count, true);
}
public SearchThread(String filter, Type reqType, SearchListenner searchListenner, boolean inverse, boolean searchJustInScope,
String baseUrl, int start, int count, boolean searchAllOccurrences) {
this(filter, reqType, searchListenner, inverse, searchJustInScope, baseUrl, start, count, searchAllOccurrences, -1);
}
public SearchThread(String filter, Type reqType, SearchListenner searchListenner, boolean inverse, boolean searchJustInScope,
String baseUrl, int start, int count, boolean searchAllOccurrences, int maxOccurrences) {
this(filter, reqType, null, searchListenner, inverse, searchJustInScope, baseUrl, start, count, searchAllOccurrences, maxOccurrences);
}
public SearchThread(String filter, Type reqType, String customSearcherName, SearchListenner searchListenner, boolean inverse,
boolean searchJustInScope, String baseUrl, int start, int count, boolean searchAllOccurrences, int maxOccurrences) {
super(THREAD_NAME);
this.filter = filter;
this.reqType = reqType;
this.customSearcherName = customSearcherName;
this.searchListenner = searchListenner;
this.inverse = inverse;
this.searchJustInScope = searchJustInScope;
this.baseUrl = baseUrl;
pcc = new PaginationConstraintsChecker(start, count, maxOccurrences);
this.searchAllOccurrences = searchAllOccurrences;
}
public void setCustomSearchers(Map<String, HttpSearcher> searchers) {
this.searchers = searchers;
}
public void stopSearch() {
this.stopSearch = true;
}
@Override
public void run() {
try {
search();
} finally {
this.searchListenner.searchComplete();
}
}
private void search() {
Session session = Model.getSingleton().getSession();
Pattern pattern = Pattern.compile(filter, Pattern.MULTILINE| Pattern.CASE_INSENSITIVE);
Matcher matcher = null;
try {
if (Type.Custom.equals(reqType)) {
if (searchers != null && customSearcherName != null) {
HttpSearcher searcher = searchers.get(customSearcherName);
if (searcher != null) {
List<SearchResult> results;
if (pcc.hasMaximumMatches()) {
results = searcher.search(pattern, inverse, pcc.getMaximumMatches());
} else {
results = searcher.search(pattern, inverse);
}
for (SearchResult sr : results) {
searchListenner.addSearchResult(sr);
}
}
}
return;
}
List<Integer> list = Model.getSingleton().getDb().getTableHistory().getHistoryIdsOfHistType(session.getSessionId(),
HistoryReference.TYPE_PROXIED, HistoryReference.TYPE_ZAP_USER, HistoryReference.TYPE_SPIDER,
HistoryReference.TYPE_SPIDER_AJAX);
int last = list.size();
int currentRecordId = 0;
for (int index=0;index < last;index++){
if (stopSearch) {
break;
}
int historyId = list.get(index).intValue();
try {
currentRecordId = index;
// Create the href to ensure the msg is set up correctly
HistoryReference href = new HistoryReference(historyId);
HttpMessage message = href.getHttpMessage();
if (searchJustInScope && ! session.isInScope(message.getRequestHeader().getURI().toString())) {
// Not in scope, so ignore
continue;
}
if (this.baseUrl != null && ! message.getRequestHeader().getURI().toString().startsWith(baseUrl)) {
// doesnt start with the specified baseurl
continue;
}
if (Type.URL.equals(reqType)) {
// URL
String url = message.getRequestHeader().getURI().toString();
matcher = pattern.matcher(url);
if (inverse && !pcc.allMatchesProcessed()) {
if (! matcher.find()) {
notifyInverseMatchFound(currentRecordId, message, SearchMatch.Location.REQUEST_HEAD);
}
} else {
int urlStartPos = message.getRequestHeader().getPrimeHeader().indexOf(url);
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.REQUEST_HEAD,
urlStartPos + matcher.start(), urlStartPos + matcher.end());
if (!searchAllOccurrences) {
break;
}
}
}
}
if (Type.Header.equals(reqType)) {
// Header
// Request header
matcher = pattern.matcher(message.getRequestHeader().toString());
if (inverse && !pcc.allMatchesProcessed()) {
if (! matcher.find()) {
notifyInverseMatchFound(currentRecordId, message, SearchMatch.Location.REQUEST_HEAD);
}
} else {
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.REQUEST_HEAD,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
}
// Response header
matcher = pattern.matcher(message.getResponseHeader().toString());
if (inverse && !pcc.allMatchesProcessed()) {
if (! matcher.find()) {
notifyInverseMatchFound(currentRecordId, message, SearchMatch.Location.RESPONSE_HEAD);
}
} else {
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.RESPONSE_HEAD,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
}
}
if (Type.Request.equals(reqType) ||
Type.All.equals(reqType)) {
if (inverse && !pcc.allMatchesProcessed()) {
// Check for no matches in either Request Header or Body
if (! pattern.matcher(message.getRequestHeader().toString()).find() &&
! pattern.matcher(message.getRequestBody().toString()).find()) {
notifyInverseMatchFound(currentRecordId, message, SearchMatch.Location.REQUEST_HEAD);
}
} else {
// Request Header
matcher = pattern.matcher(message.getRequestHeader().toString());
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.REQUEST_HEAD,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
// Request Body
matcher = pattern.matcher(message.getRequestBody().toString());
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.REQUEST_BODY,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
}
}
if (Type.Response.equals(reqType) ||
Type.All.equals(reqType)) {
if (inverse && !pcc.allMatchesProcessed()) {
// Check for no matches in either Response Header or Body
if (! pattern.matcher(message.getResponseHeader().toString()).find() &&
! pattern.matcher(message.getResponseBody().toString()).find()) {
notifyInverseMatchFound(currentRecordId, message, SearchMatch.Location.RESPONSE_HEAD);
}
} else {
// Response header
matcher = pattern.matcher(message.getResponseHeader().toString());
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.RESPONSE_HEAD,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
// Response body
matcher = pattern.matcher(message.getResponseBody().toString());
while (matcher.find() && !pcc.allMatchesProcessed()) {
notifyMatchFound(currentRecordId, matcher.group(), message, SearchMatch.Location.RESPONSE_BODY,
matcher.start(), matcher.end());
if (!searchAllOccurrences) {
break;
}
}
}
}
} catch (HttpMalformedHeaderException e1) {
log.error(e1.getMessage(), e1);
}
if (pcc.hasPageEnded()) {
break;
}
}
} catch (DatabaseException e) {
log.error(e.getMessage(), e);
}
}
private void notifyInverseMatchFound(int currentRecordId, HttpMessage message, SearchMatch.Location location) {
notifyMatchFound(currentRecordId, "", message, location, 0, 0);
}
private void notifyMatchFound(int currentRecordId, String stringFound, HttpMessage message, SearchMatch.Location location, int start, int end) {
pcc.recordProcessed(currentRecordId);
if (!pcc.hasPageStarted()) {
// Before the specified start
return;
}
pcc.matchProcessed();
searchListenner.addSearchResult(new SearchResult(reqType, filter, stringFound, new SearchMatch(
message,
location,
start,
end)));
}
private static class PaginationConstraintsChecker {
private boolean pageStarted;
private boolean pageEnded;
private final int startRecord;
private final boolean hasEnd;
private final int finalRecord;
private int recordsProcessed;
private int currentRecordId;
private final int maximumMatches;
private final boolean hasMaximumMatches;
private boolean allMatchesProcessed;
private int matchesProcessed;
public PaginationConstraintsChecker(int start, int count, int matches) {
recordsProcessed = 0;
matchesProcessed = 0;
currentRecordId = -1;
if (start > 0) {
pageStarted = false;
startRecord = start;
} else {
pageStarted = true;
startRecord = 0;
}
if (count > 0) {
hasEnd = true;
finalRecord = !pageStarted ? start + count - 1 : count;
} else {
hasEnd = false;
finalRecord = 0;
}
pageEnded = false;
allMatchesProcessed = false;
maximumMatches = matches;
hasMaximumMatches = maximumMatches > 0;
}
public boolean hasMaximumMatches() {
return hasMaximumMatches;
}
public int getMaximumMatches() {
return maximumMatches;
}
public void recordProcessed(int recordId) {
if (currentRecordId == recordId) {
return;
}
currentRecordId = recordId;
++recordsProcessed;
if (!pageStarted) {
pageStarted = recordsProcessed >= startRecord;
}
if (hasEnd && !pageEnded) {
pageEnded = recordsProcessed >= finalRecord;
}
}
public void matchProcessed() {
++matchesProcessed;
if (hasMaximumMatches && matchesProcessed >= maximumMatches) {
allMatchesProcessed = true;
pageEnded = true;
}
}
public boolean hasPageStarted() {
return pageStarted;
}
public boolean hasPageEnded() {
return pageEnded;
}
public boolean allMatchesProcessed() {
return allMatchesProcessed;
}
}
}