/*
* 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.search;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.sf.json.JSONObject;
import org.apache.log4j.Logger;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.db.RecordHistory;
import org.parosproxy.paros.db.TableHistory;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.extension.api.API;
import org.zaproxy.zap.extension.api.ApiException;
import org.zaproxy.zap.extension.api.ApiImplementor;
import org.zaproxy.zap.extension.api.ApiOther;
import org.zaproxy.zap.extension.api.ApiResponse;
import org.zaproxy.zap.extension.api.ApiResponseConversionUtils;
import org.zaproxy.zap.extension.api.ApiResponseList;
import org.zaproxy.zap.extension.api.ApiResponseSet;
import org.zaproxy.zap.extension.api.ApiView;
import org.zaproxy.zap.utils.HarUtils;
import edu.umass.cs.benchlab.har.HarEntries;
import edu.umass.cs.benchlab.har.HarLog;
public class SearchAPI extends ApiImplementor {
private static Logger log = Logger.getLogger(SearchAPI.class);
private static final String PREFIX = "search";
private static final String VIEW_URLS_BY_URL_REGEX = "urlsByUrlRegex";
private static final String VIEW_URLS_BY_REQUEST_REGEX = "urlsByRequestRegex";
private static final String VIEW_URLS_BY_RESPONSE_REGEX = "urlsByResponseRegex";
private static final String VIEW_URLS_BY_HEADER_REGEX = "urlsByHeaderRegex";
private static final String VIEW_MESSAGES_BY_URL_REGEX = "messagesByUrlRegex";
private static final String VIEW_MESSAGES_BY_REQUEST_REGEX = "messagesByRequestRegex";
private static final String VIEW_MESSAGES_BY_RESPONSE_REGEX = "messagesByResponseRegex";
private static final String VIEW_MESSAGES_BY_HEADER_REGEX = "messagesByHeaderRegex";
private static final String OTHER_HAR_BY_URL_REGEX = "harByUrlRegex";
private static final String OTHER_HAR_BY_REQUEST_REGEX = "harByRequestRegex";
private static final String OTHER_HAR_BY_RESPONSE_REGEX = "harByResponseRegex";
private static final String OTHER_HAR_BY_HEADER_REGEX = "harByHeaderRegex";
private static final String PARAM_BASE_URL = "baseurl";
//private static final String PARAM_CONTEXT_ID = "contextId"; TODO: implement
private static final String PARAM_COUNT = "count";
private static final String PARAM_REGEX = "regex";
private static final String PARAM_START = "start";
private enum SearchViewResponseType {
URL,
MESSAGE
}
private ExtensionSearch extension;
public SearchAPI (ExtensionSearch extension) {
this.extension = extension;
final String[] searchMandatoryParams = new String[] { PARAM_REGEX };
final String[] searchOptionalParams = new String[] { PARAM_BASE_URL, PARAM_START, PARAM_COUNT };
this.addApiView(new ApiView(VIEW_URLS_BY_URL_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_URLS_BY_REQUEST_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_URLS_BY_RESPONSE_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_URLS_BY_HEADER_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_MESSAGES_BY_URL_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_MESSAGES_BY_REQUEST_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_MESSAGES_BY_RESPONSE_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiView(new ApiView(VIEW_MESSAGES_BY_HEADER_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiOthers(new ApiOther(OTHER_HAR_BY_URL_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiOthers(new ApiOther(OTHER_HAR_BY_REQUEST_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiOthers(new ApiOther(OTHER_HAR_BY_RESPONSE_REGEX, searchMandatoryParams, searchOptionalParams));
this.addApiOthers(new ApiOther(OTHER_HAR_BY_HEADER_REGEX, searchMandatoryParams, searchOptionalParams));
}
@Override
public String getPrefix() {
return PREFIX;
}
@Override
public ApiResponse handleApiView(final String name, JSONObject params)
throws ApiException {
final ApiResponseList result = new ApiResponseList(name);
ExtensionSearch.Type searchType;
SearchViewResponseType responseType;
switch (name) {
case VIEW_URLS_BY_URL_REGEX:
searchType = ExtensionSearch.Type.URL;
responseType = SearchViewResponseType.URL;
break;
case VIEW_MESSAGES_BY_URL_REGEX:
searchType = ExtensionSearch.Type.URL;
responseType = SearchViewResponseType.MESSAGE;
break;
case VIEW_URLS_BY_REQUEST_REGEX:
searchType = ExtensionSearch.Type.Request;
responseType = SearchViewResponseType.URL;
break;
case VIEW_MESSAGES_BY_REQUEST_REGEX:
searchType = ExtensionSearch.Type.Request;
responseType = SearchViewResponseType.MESSAGE;
break;
case VIEW_URLS_BY_RESPONSE_REGEX:
searchType = ExtensionSearch.Type.Response;
responseType = SearchViewResponseType.URL;
break;
case VIEW_MESSAGES_BY_RESPONSE_REGEX:
searchType = ExtensionSearch.Type.Response;
responseType = SearchViewResponseType.MESSAGE;
break;
case VIEW_URLS_BY_HEADER_REGEX:
searchType = ExtensionSearch.Type.Header;
responseType = SearchViewResponseType.URL;
break;
case VIEW_MESSAGES_BY_HEADER_REGEX:
searchType = ExtensionSearch.Type.Header;
responseType = SearchViewResponseType.MESSAGE;
break;
default:
throw new ApiException(ApiException.Type.BAD_VIEW);
}
validateRegex(params);
try {
SearchResultsProcessor processor;
if (SearchViewResponseType.MESSAGE == responseType) {
processor = new SearchResultsProcessor() {
@Override
public void processRecordHistory(RecordHistory recordHistory) {
result.addItem(ApiResponseConversionUtils.httpMessageToSet(
recordHistory.getHistoryId(),
recordHistory.getHistoryType(),
recordHistory.getHttpMessage()));
}
};
} else {
processor = new SearchResultsProcessor() {
@Override
public void processRecordHistory(RecordHistory recordHistory) {
final HttpMessage msg = recordHistory.getHttpMessage();
Map<String, String> map = new HashMap<>();
map.put("id", String.valueOf(recordHistory.getHistoryId()));
map.put("type", String.valueOf(recordHistory.getHistoryType()));
map.put("method", msg.getRequestHeader().getMethod());
map.put("url", msg.getRequestHeader().getURI().toString());
map.put("code", String.valueOf(msg.getResponseHeader().getStatusCode()));
map.put("time", String.valueOf(msg.getTimeElapsedMillis()));
result.addItem(new ApiResponseSet<String>(name, map));
}
};
}
search(params, searchType, processor);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ApiException(ApiException.Type.INTERNAL_ERROR,
e.getMessage());
}
return result;
}
private static void validateRegex(JSONObject params) throws ApiException {
try {
Pattern.compile(params.getString(PARAM_REGEX));
} catch (NullPointerException | PatternSyntaxException e) {
throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_REGEX, e);
}
}
@Override
public HttpMessage handleApiOther(HttpMessage msg, String name, JSONObject params) throws ApiException {
byte responseBody[] = {};
ExtensionSearch.Type searchType;
switch (name) {
case OTHER_HAR_BY_URL_REGEX:
searchType = ExtensionSearch.Type.URL;
break;
case OTHER_HAR_BY_REQUEST_REGEX:
searchType = ExtensionSearch.Type.Request;
break;
case OTHER_HAR_BY_RESPONSE_REGEX:
searchType = ExtensionSearch.Type.Response;
break;
case OTHER_HAR_BY_HEADER_REGEX:
searchType = ExtensionSearch.Type.Header;
break;
default:
throw new ApiException(ApiException.Type.BAD_OTHER);
}
validateRegex(params);
try {
final HarEntries entries = new HarEntries();
search(params, searchType, new SearchResultsProcessor() {
@Override
public void processRecordHistory(RecordHistory recordHistory) {
entries.addEntry(HarUtils.createHarEntry(recordHistory.getHttpMessage()));
}
});
HarLog harLog = HarUtils.createZapHarLog();
harLog.setEntries(entries);
responseBody = HarUtils.harLogToByteArray(harLog);
} catch (Exception e) {
log.error(e.getMessage(), e);
ApiException apiException = new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage());
responseBody = apiException.toString(API.Format.JSON, incErrorDetails()).getBytes(StandardCharsets.UTF_8);
}
try {
msg.setResponseHeader(API.getDefaultResponseHeader("application/json; charset=UTF-8", responseBody.length));
} catch (HttpMalformedHeaderException e) {
log.error("Failed to create response header: " + e.getMessage(), e);
}
msg.setResponseBody(responseBody);
return msg;
}
private boolean incErrorDetails() {
return Model.getSingleton().getOptionsParam().getApiParam().isIncErrorDetails();
}
private void search(JSONObject params, ExtensionSearch.Type searchType, SearchResultsProcessor processor)
throws InterruptedException {
ApiSearchListener searchListener = new ApiSearchListener();
// The search kicks off a background thread
extension.search(
params.getString(PARAM_REGEX),
searchListener,
searchType,
false,
false,
this.getParam(params, PARAM_BASE_URL, (String) null),
this.getParam(params, PARAM_START, -1),
this.getParam(params, PARAM_COUNT, -1),
false);
while (!searchListener.isSearchComplete()) {
Thread.sleep(100);
}
TableHistory tableHistory = Model.getSingleton().getDb().getTableHistory();
for (Integer hRefId : searchListener.getHistoryReferencesIds()) {
try {
processor.processRecordHistory(tableHistory.read(hRefId.intValue()));
} catch (DatabaseException | HttpMalformedHeaderException e) {
log.error(e.getMessage(), e);
}
}
}
private interface SearchResultsProcessor {
void processRecordHistory(RecordHistory recordHistory);
}
private static class ApiSearchListener implements SearchListenner {
private boolean searchComplete;
private List<Integer> historyReferencesIds;
public ApiSearchListener() {
super();
searchComplete = false;
historyReferencesIds = new LinkedList<>();
}
@Override
public void addSearchResult(SearchResult sr) {
historyReferencesIds.add(Integer.valueOf(sr.getMessage().getHistoryRef().getHistoryId()));
}
@Override
public void searchComplete() {
this.searchComplete = true;
}
public boolean isSearchComplete() {
return searchComplete;
}
public List<Integer> getHistoryReferencesIds() {
return historyReferencesIds;
}
}
}