package io.undertow.server.handlers;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.ResponseCommitListener;
import io.undertow.server.handlers.builder.HandlerBuilder;
import io.undertow.util.Headers;
import io.undertow.util.SameSiteNoneIncompatibleClientChecker;
public class SameSiteCookieHandler implements HttpHandler {
private final HttpHandler next;
private final String mode;
private final Pattern cookiePattern;
private final boolean enableClientChecker;
private final boolean addSecureForNone;
public SameSiteCookieHandler(final HttpHandler next, final String mode) {
this(next, mode, null, true, true, true);
}
public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern) {
this(next, mode, cookiePattern, true, true, true);
}
public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern, final boolean caseSensitive) {
this(next, mode, cookiePattern, caseSensitive, true, true);
}
public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern, final boolean caseSensitive, final boolean enableClientChecker, final boolean addSecureForNone) {
this.next = next;
this.mode = mode;
if (cookiePattern != null && !cookiePattern.isEmpty()) {
this.cookiePattern = Pattern.compile(cookiePattern, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
} else {
this.cookiePattern = null;
}
final boolean modeIsNone = CookieSameSiteMode.NONE.toString().equalsIgnoreCase(mode);
this.enableClientChecker = enableClientChecker && modeIsNone;
this.addSecureForNone = addSecureForNone && modeIsNone;
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (mode != null) {
exchange.addResponseCommitListener(new ResponseCommitListener() {
@Override
public void beforeCommit(HttpServerExchange exchange) {
String userAgent = exchange.getRequestHeaders().getFirst(Headers.USER_AGENT);
if (enableClientChecker && userAgent != null && !SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent)) {
return;
}
for (Cookie cookie : exchange.responseCookies()) {
if (cookiePattern == null || cookiePattern.matcher(cookie.getName()).matches()) {
cookie.setSameSiteMode(mode);
if (addSecureForNone) {
cookie.setSecure(true);
}
}
}
}
});
}
next.handleRequest(exchange);
}
public static class Builder implements HandlerBuilder {
@Override
public String name() {
return "samesite-cookie";
}
@Override
public Map<String, Class<?>> parameters() {
Map<String, Class<?>> parameters = new HashMap<>();
parameters.put("mode", String.class);
parameters.put("cookie-pattern", String.class);
parameters.put("case-sensitive", Boolean.class);
parameters.put("enable-client-checker", Boolean.class);
parameters.put("add-secure-for-none", Boolean.class);
return parameters;
}
@Override
public Set<String> requiredParameters() {
return Collections.singleton("mode");
}
@Override
public String defaultParameter() {
return "mode";
}
@Override
public HandlerWrapper build(Map<String, Object> config) {
final String mode = (String) config.get("mode");
final String pattern = (String) config.get("cookie-pattern");
final Boolean caseSensitive = (Boolean) config.get("case-sensitive");
final Boolean enableClientChecker = (Boolean) config.get("enable-client-checker");
final Boolean addSecureForNone = (Boolean) config.get("add-secure-for-none");
return new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
return new SameSiteCookieHandler(handler, mode, pattern,
caseSensitive == null ? true : caseSensitive,
enableClientChecker == null ? true : enableClientChecker,
addSecureForNone == null ? true : addSecureForNone);
}
};
}
}
}