package com.networknt.sanitizer;
import com.networknt.body.BodyHandler;
import com.networknt.config.Config;
import com.networknt.handler.MiddlewareHandler;
import com.networknt.utility.ModuleRegistry;
import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderMap;
import io.undertow.util.HeaderValues;
import org.owasp.encoder.Encode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* This is a middleware component that sanitize cross site scripting tags in request. As potentially
* sanitizing body of the request, this middleware must be plugged into the chain after body parser.
*
* Created by steve on 22/10/16.
*/
public class SanitizerHandler implements MiddlewareHandler {
public static final String CONFIG_NAME = "sanitizer";
static final Logger logger = LoggerFactory.getLogger(SanitizerHandler.class);
static SanitizerConfig config = (SanitizerConfig) Config.getInstance().getJsonObjectConfig(CONFIG_NAME, SanitizerConfig.class);
private volatile HttpHandler next;
public SanitizerHandler() {
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
String method = exchange.getRequestMethod().toString();
if(config.isSanitizeHeader()) {
HeaderMap headerMap = exchange.getRequestHeaders();
if(headerMap != null) {
for (HeaderValues values : headerMap) {
if (values != null) {
ListIterator<String> itValues = values.listIterator();
while (itValues.hasNext()) {
String value = Encode.forJavaScriptSource(itValues.next());
itValues.set(value);
}
}
}
}
}
/*
It looks like undertow has done a lot of things to prevent passing in invalid query parameters,
Until there are some use cases, this is not implemented.
if(config.isSanitizeParameter()) {
if (!exchange.getQueryString().isEmpty()) {
final TreeMap<String, Deque<String>> newParams = new TreeMap<>();
for (Map.Entry<String, Deque<String>> param : exchange.getQueryParameters().entrySet()) {
final Deque<String> newVales = new ArrayDeque<>(param.getValue().size());
for (String val : param.getValue()) {
newVales.add(Encode.forJavaScriptSource(val));
}
newParams.put(param.getKey(), newVales);
}
exchange.getQueryParameters().clear();
exchange.getQueryParameters().putAll(newParams);
}
}
*/
if(config.isSanitizeBody() && ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method))) {
// assume that body parser is installed before this middleware and body is parsed as a map.
// we are talking about JSON api now.
Object body = exchange.getAttachment(BodyHandler.REQUEST_BODY);
if(body != null) {
if(body instanceof List) {
encodeList((List<Map<String, Object>>)body);
} else {
// assume it is a map here.
encodeNode((Map<String, Object>)body);
}
}
}
next.handleRequest(exchange);
}
@Override
public HttpHandler getNext() {
return next;
}
@Override
public MiddlewareHandler setNext(final HttpHandler next) {
Handlers.handlerNotNull(next);
this.next = next;
return this;
}
@Override
public boolean isEnabled() {
return config.isEnabled();
}
@Override
public void register() {
ModuleRegistry.registerModule(SanitizerHandler.class.getName(), Config.getInstance().getJsonMapConfigNoCache(CONFIG_NAME), null);
}
public void encodeNode(Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String)
map.put(key, Encode.forJavaScriptSource((String) value));
else if (value instanceof Map)
encodeNode((Map) value);
else if (value instanceof List) {
encodeList((List)value);
}
}
}
public void encodeList(List list) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof String) {
list.set(i, Encode.forJavaScriptSource((String)list.get(i)));
} else if(list.get(i) instanceof Map) {
encodeNode((Map<String, Object>)list.get(i));
} else if(list.get(i) instanceof List) {
encodeList((List)list.get(i));
}
}
}
}