/*
* 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.parosproxy.paros.core.scanner;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.log4j.Logger;
import org.parosproxy.paros.network.HttpMessage;
/**
* Specialized variant able to handle the filter parameters of OData URIs (focused on OData v2)
* <p>
* Example of query:<br>
* http://services.odata.org/OData/OData.svc/Product?$filter=startswith(name,'Foo')
* and price lt 10
* <p>
* References:
* <ul>
* <li>http://www.odata.org/documentation/uri-conventions</li>
* <li>http://msdn.microsoft.com/en-us/library/gg309461.aspx#BKMK_filter</li>
* </ul>
*
* TODO:<br>
* - Properly handle escaped vs. unescaped parameters<br/>
* - Handle OData functions (startwith, substringof, ...)<br/>
*
*/
public class VariantODataFilterQuery implements Variant {
private static final Logger log = Logger.getLogger(VariantODataFilterQuery.class);
// Extract the content of the $filter parameter
private static final Pattern patternFilterParameters = Pattern.compile("\\$filter[ ]*=[ ]*([\\w\\s()',./\\-:]*)");
// Extract the effective parameters from the $filter string
// TODO: Support complex expressions
private static final Pattern patternParameters = Pattern.compile("([\\w]+)\\s+(eq|ne|gt|ge|lt|le|and|or|not)\\s+([\\w'/]+)");
// Store the URI parts located before and after the filter expression
private String beforeFilterExpression = null;
private String afterFilterExpression = null;
/**
* Storage for the operation parameters
*/
private Map<String, OperationParameter> mapParameters = Collections.emptyMap();
@Override
public void setMessage(HttpMessage msg) {
URI uri = msg.getRequestHeader().getURI();
parse(uri);
}
private void parse(URI uri) {
try {
String query = uri.getQuery();
// Detection of a filter statement if any
if (query != null) {
Matcher matcher = patternFilterParameters.matcher(query);
if (matcher.find()) {
String filterExpression = "";
filterExpression = matcher.group(1);
int begin = query.indexOf(filterExpression);
int end = begin + filterExpression.length();
beforeFilterExpression = query.substring(0, begin);
afterFilterExpression = query.substring(end);
// Now scan the expression in order to identify all parameters
mapParameters = new HashMap<>();
Matcher matcherParameters = patternParameters.matcher(filterExpression);
while (matcherParameters.find()) {
String nameOpAndValue = matcherParameters.group(0);
String paramName = matcherParameters.group(1);
String operator = matcherParameters.group(2);
String paramValue = matcherParameters.group(3);
begin = filterExpression.indexOf(nameOpAndValue);
end = begin + nameOpAndValue.length();
String before = filterExpression.substring(0, begin);
String after = filterExpression.substring(end);
OperationParameter opParam = new OperationParameter(paramName, operator, paramValue, before, after);
mapParameters.put(opParam.getParameterName(), opParam);
}
} else {
beforeFilterExpression = null;
afterFilterExpression = null;
mapParameters = Collections.emptyMap();
}
} else {
beforeFilterExpression = null;
afterFilterExpression = null;
mapParameters = Collections.emptyMap();
}
} catch (URIException e) {
log.error(e.getMessage() + uri, e);
}
}
@Override
public Vector<NameValuePair> getParamList() {
Vector<NameValuePair> out = new Vector<>(mapParameters.values().size());
int i = 1;
for (OperationParameter opParam : mapParameters.values()) {
out.add(new NameValuePair(NameValuePair.TYPE_QUERY_STRING, opParam.getParameterName(), opParam.getValue(), i++));
}
return out;
}
@Override
public String setParameter(HttpMessage msg, NameValuePair originalPair, String param, String value) {
// TODO: Implement correctly escaped / non-escaped params
OperationParameter opParam = mapParameters.get(param);
if (opParam != null) {
String newfilter = opParam.getModifiedFilter(value);
String modifiedQuery = beforeFilterExpression + newfilter + afterFilterExpression;
try {
msg.getRequestHeader().getURI().setQuery(modifiedQuery);
} catch (URIException | NullPointerException e) {
log.error("Exception with the modified query " + modifiedQuery, e);
}
return newfilter;
}
return null;
}
@Override
public String setEscapedParameter(HttpMessage msg, NameValuePair originalPair, String param, String value) {
// TODO: Implement correctly escaped / non-escaped params
return setParameter(msg, originalPair, param, value);
}
/**
* Store a parameter and related data
*/
static class OperationParameter {
private String paramName;
private String operator;
private String originalValue;
private String stringBeforeOperation;
private String stringAfterOperation;
/**
* Constructs an {@code OperationParameter} with the given name, operator, value and surrounding strings.
*
* @param name the name
* @param operator the operator
* @param value the value
* @param stringBeforeOperation the string before the operation (parameter + operator + value)
* @param stringAfterOperation the string after the operation (parameter + operator + value)
*/
public OperationParameter(String name, String operator, String value, String stringBeforeOperation, String stringAfterOperation) {
super();
this.paramName = name;
this.operator = operator;
this.originalValue = value;
this.stringBeforeOperation = stringBeforeOperation;
this.stringAfterOperation = stringAfterOperation;
}
/**
* Gets the value of the parameter.
*
* @return the value of the parameter
*/
public String getValue() {
return this.originalValue;
}
public String getParameterName() {
return this.paramName;
}
public String getModifiedFilter(String newIdValue) {
StringBuilder builder = new StringBuilder();
builder.append(this.stringBeforeOperation)
.append(this.paramName)
.append(' ')
.append(this.operator)
.append(' ')
.append(newIdValue)
.append(this.stringAfterOperation);
return builder.toString();
}
}
}