package org.apache.catalina.core;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.SessionTrackingMode;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.PushBuilder;
import org.apache.catalina.Context;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.connector.Request;
import org.apache.catalina.util.SessionConfig;
import org.apache.coyote.ActionCode;
import org.apache.tomcat.util.buf.HexUtils;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
import org.apache.tomcat.util.http.CookieProcessor;
import org.apache.tomcat.util.http.parser.HttpParser;
import org.apache.tomcat.util.res.StringManager;
public class ApplicationPushBuilder implements PushBuilder {
private static final StringManager sm = StringManager.getManager(ApplicationPushBuilder.class);
private static final Set<String> DISALLOWED_METHODS = new HashSet<>();
static {
DISALLOWED_METHODS.add("POST");
DISALLOWED_METHODS.add("PUT");
DISALLOWED_METHODS.add("DELETE");
DISALLOWED_METHODS.add("CONNECT");
DISALLOWED_METHODS.add("OPTIONS");
DISALLOWED_METHODS.add("TRACE");
}
private final HttpServletRequest baseRequest;
private final Request catalinaRequest;
private final org.apache.coyote.Request coyoteRequest;
private final String sessionCookieName;
private final String sessionPathParameterName;
private final boolean addSessionCookie;
private final boolean addSessionPathParameter;
private final Map<String,List<String>> = new CaseInsensitiveKeyMap<>();
private final List<Cookie> cookies = new ArrayList<>();
private String method = "GET";
private String path;
private String queryString;
private String sessionId;
private String userName;
public ApplicationPushBuilder(Request catalinaRequest, HttpServletRequest request) {
baseRequest = request;
this.catalinaRequest = catalinaRequest;
coyoteRequest = catalinaRequest.getCoyoteRequest();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
List<String> values = new ArrayList<>();
headers.put(headerName, values);
Enumeration<String> headerValues = request.getHeaders(headerName);
while (headerValues.hasMoreElements()) {
values.add(headerValues.nextElement());
}
}
headers.remove("if-match");
headers.remove("if-none-match");
headers.remove("if-modified-since");
headers.remove("if-unmodified-since");
headers.remove("if-range");
headers.remove("range");
headers.remove("expect");
headers.remove("authorization");
headers.remove("referer");
headers.remove("cookie");
StringBuffer referer = request.getRequestURL();
if (request.getQueryString() != null) {
referer.append('?');
referer.append(request.getQueryString());
}
addHeader("referer", referer.toString());
Context context = catalinaRequest.getContext();
sessionCookieName = SessionConfig.getSessionCookieName(context);
sessionPathParameterName = SessionConfig.getSessionUriParamName(context);
HttpSession session = request.getSession(false);
if (session != null) {
sessionId = session.getId();
}
if (sessionId == null) {
sessionId = request.getRequestedSessionId();
}
if (!request.isRequestedSessionIdFromCookie() && !request.isRequestedSessionIdFromURL() &&
sessionId != null) {
Set<SessionTrackingMode> sessionTrackingModes =
request.getServletContext().getEffectiveSessionTrackingModes();
addSessionCookie = sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
addSessionPathParameter = sessionTrackingModes.contains(SessionTrackingMode.URL);
} else {
addSessionCookie = request.isRequestedSessionIdFromCookie();
addSessionPathParameter = request.isRequestedSessionIdFromURL();
}
if (request.getCookies() != null) {
cookies.addAll(Arrays.asList(request.getCookies()));
}
for (Cookie responseCookie : catalinaRequest.getResponse().getCookies()) {
if (responseCookie.getMaxAge() < 0) {
cookies.removeIf(cookie -> cookie.getName().equals(responseCookie.getName()));
} else {
cookies.add(new Cookie(responseCookie.getName(), responseCookie.getValue()));
}
}
List<String> cookieValues = new ArrayList<>(1);
cookieValues.add(generateCookieHeader(cookies,
catalinaRequest.getContext().getCookieProcessor()));
headers.put("cookie", cookieValues);
if (catalinaRequest.getPrincipal() != null) {
if ((session == null) || catalinaRequest.getSessionInternal(false).getPrincipal() == null
|| !(context.getAuthenticator() instanceof AuthenticatorBase)
|| !((AuthenticatorBase) context.getAuthenticator()).getCache()) {
userName = catalinaRequest.getPrincipal().getName();
}
setHeader("authorization", "x-push");
}
}
@Override
public PushBuilder path(String path) {
if (path.startsWith("/")) {
this.path = path;
} else {
String contextPath = baseRequest.getContextPath();
int len = contextPath.length() + path.length() + 1;
StringBuilder sb = new StringBuilder(len);
sb.append(contextPath);
sb.append('/');
sb.append(path);
this.path = sb.toString();
}
return this;
}
@Override
public String getPath() {
return path;
}
@Override
public PushBuilder method(String method) {
String upperMethod = method.trim().toUpperCase(Locale.ENGLISH);
if (DISALLOWED_METHODS.contains(upperMethod) || upperMethod.length() == 0) {
throw new IllegalArgumentException(
sm.getString("applicationPushBuilder.methodInvalid", upperMethod));
}
for (char c : upperMethod.toCharArray()) {
if (!HttpParser.isToken(c)) {
throw new IllegalArgumentException(
sm.getString("applicationPushBuilder.methodNotToken", upperMethod));
}
}
this.method = method;
return this;
}
@Override
public String getMethod() {
return method;
}
@Override
public PushBuilder queryString(String queryString) {
this.queryString = queryString;
return this;
}
@Override
public String getQueryString() {
return queryString;
}
@Override
public PushBuilder sessionId(String sessionId) {
this.sessionId = sessionId;
return this;
}
@Override
public String getSessionId() {
return sessionId;
}
@Override
public PushBuilder (String name, String value) {
List<String> values = headers.get(name);
if (values == null) {
values = new ArrayList<>();
headers.put(name, values);
}
values.add(value);
return this;
}
@Override
public PushBuilder (String name, String value) {
List<String> values = headers.get(name);
if (values == null) {
values = new ArrayList<>();
headers.put(name, values);
} else {
values.clear();
}
values.add(value);
return this;
}
@Override
public PushBuilder (String name) {
headers.remove(name);
return this;
}
@Override
public Set<String> () {
return Collections.unmodifiableSet(headers.keySet());
}
@Override
public String (String name) {
List<String> values = headers.get(name);
if (values == null) {
return null;
} else {
return values.get(0);
}
}
@Override
public void push() {
if (path == null) {
throw new IllegalStateException(sm.getString("pushBuilder.noPath"));
}
org.apache.coyote.Request pushTarget = new org.apache.coyote.Request();
pushTarget.method().setString(method);
pushTarget.serverName().setString(baseRequest.getServerName());
pushTarget.setServerPort(baseRequest.getServerPort());
pushTarget.scheme().setString(baseRequest.getScheme());
for (Map.Entry<String,List<String>> header : headers.entrySet()) {
for (String value : header.getValue()) {
pushTarget.getMimeHeaders().addValue(header.getKey()).setString(value);
}
}
int queryIndex = path.indexOf('?');
String pushPath;
String pushQueryString = null;
if (queryIndex > -1) {
pushPath = path.substring(0, queryIndex);
if (queryIndex + 1 < path.length()) {
pushQueryString = path.substring(queryIndex + 1);
}
} else {
pushPath = path;
}
if (sessionId != null) {
if (addSessionPathParameter) {
pushPath = pushPath + ";" + sessionPathParameterName + "=" + sessionId;
pushTarget.addPathParameter(sessionPathParameterName, sessionId);
}
if (addSessionCookie) {
String sessionCookieHeader = sessionCookieName + "=" + sessionId;
MessageBytes mb = pushTarget.getMimeHeaders().getValue("cookie");
if (mb == null) {
mb = pushTarget.getMimeHeaders().addValue("cookie");
mb.setString(sessionCookieHeader);
} else {
mb.setString(mb.getString() + ";" + sessionCookieHeader);
}
}
}
pushTarget.requestURI().setString(pushPath);
pushTarget.decodedURI().setString(decode(pushPath,
catalinaRequest.getConnector().getURICharset()));
if (pushQueryString == null && queryString != null) {
pushTarget.queryString().setString(queryString);
} else if (pushQueryString != null && queryString == null) {
pushTarget.queryString().setString(pushQueryString);
} else if (pushQueryString != null && queryString != null) {
pushTarget.queryString().setString(pushQueryString + "&" +queryString);
}
if (userName != null) {
pushTarget.getRemoteUser().setString(userName);
pushTarget.setRemoteUserNeedsAuthorization(true);
}
coyoteRequest.action(ActionCode.PUSH_REQUEST, pushTarget);
path = null;
headers.remove("if-none-match");
headers.remove("if-modified-since");
}
static String decode(String input, Charset charset) {
int start = input.indexOf('%');
int end = 0;
if (start == -1) {
return input;
}
StringBuilder result = new StringBuilder(input.length());
while (start != -1) {
result.append(input.substring(end, start));
end = start + 3;
while (end <input.length() && input.charAt(end) == '%') {
end += 3;
}
result.append(decodePercentSequence(input.substring(start, end), charset));
start = input.indexOf('%', end);
}
result.append(input.substring(end));
return result.toString();
}
private static String decodePercentSequence(String sequence, Charset charset) {
byte[] bytes = new byte[sequence.length()/3];
for (int i = 0; i < bytes.length; i += 3) {
bytes[i] = (byte) ((HexUtils.getDec(sequence.charAt(1 + 3 * i)) << 4) +
HexUtils.getDec(sequence.charAt(2 + 3 * i)));
}
return new String(bytes, charset);
}
private static String (List<Cookie> cookies, CookieProcessor cookieProcessor) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Cookie cookie : cookies) {
if (first) {
first = false;
} else {
result.append(';');
}
result.append(cookieProcessor.generateHeader(cookie, null));
}
return result.toString();
}
}