io.undertow.server.Connectors Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 io.undertow.server;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import io.netty.buffer.ByteBuf;
import io.undertow.UndertowLogger;
import io.undertow.httpcore.HttpHeaderNames;
import io.undertow.httpcore.StatusCodes;
import io.undertow.httpcore.UndertowOptions;
import io.undertow.server.handlers.Cookie;
import io.undertow.util.DateUtils;
import io.undertow.util.LegacyCookieSupport;
import io.undertow.util.ParameterLimitException;
import io.undertow.util.URLUtils;
/**
* This class provides the connector part of the {@link HttpServerExchange} API.
*
* It contains methods that logically belong on the exchange, however should only be used
* by connector implementations.
*
* @author Stuart Douglas
*/
public class Connectors {
/**
* Flattens the exchange cookie map into the response header map. This should be called by a
* connector just before the response is started.
*
* @param exchange The server exchange
*/
public static void flattenCookies(final HttpServerExchange exchange) {
Map cookies = exchange.getResponseCookiesInternal();
boolean enableRfc6265Validation = exchange.getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION);
if (cookies != null) {
for (Map.Entry entry : cookies.entrySet()) {
exchange.addResponseHeader(HttpHeaderNames.SET_COOKIE, getCookieString(entry.getValue(), enableRfc6265Validation));
}
}
}
public static boolean isRunningHandlerChain(HttpServerExchange exchange) {
return exchange.isExecutingHandlerChain();
}
/**
* Attached buffered data to the exchange. The will generally be used to allow data to be re-read.
*
* @param exchange The HTTP server exchange
* @param buffer The buffers to attach
*/
public static void ungetRequestBytes(final HttpServerExchange exchange, ByteBuf buffer) {
throw new RuntimeException("NYI");
// exchange.ungetRequestBytes(buffer, exchange);
// exchange.resetRequestChannel();
}
private static String getCookieString(final Cookie cookie, boolean enableRfc6265Validation) {
if (enableRfc6265Validation) {
return addRfc6265ResponseCookieToExchange(cookie);
} else {
switch (LegacyCookieSupport.adjustedCookieVersion(cookie)) {
case 0:
return addVersion0ResponseCookieToExchange(cookie);
case 1:
default:
return addVersion1ResponseCookieToExchange(cookie);
}
}
}
public static void setRequestStartTime(HttpServerExchange exchange) {
exchange.setRequestStartTime(System.nanoTime());
}
public static void setRequestStartTime(HttpServerExchange existing, HttpServerExchange newExchange) {
newExchange.setRequestStartTime(existing.getRequestStartTime());
}
private static String addRfc6265ResponseCookieToExchange(final Cookie cookie) {
final StringBuilder header = new StringBuilder(cookie.getName());
header.append("=");
if (cookie.getValue() != null) {
header.append(cookie.getValue());
}
if (cookie.getPath() != null) {
header.append("; Path=");
header.append(cookie.getPath());
}
if (cookie.getDomain() != null) {
header.append("; Domain=");
header.append(cookie.getDomain());
}
if (cookie.isDiscard()) {
header.append("; Discard");
}
if (cookie.isSecure()) {
header.append("; Secure");
}
if (cookie.isHttpOnly()) {
header.append("; HttpOnly");
}
if (cookie.getMaxAge() != null) {
if (cookie.getMaxAge() >= 0) {
header.append("; Max-Age=");
header.append(cookie.getMaxAge());
}
// Microsoft IE and Microsoft Edge don't understand Max-Age so send
// expires as well. Without this, persistent cookies fail with those
// browsers. They do understand Expires, even with V1 cookies.
// So, we add Expires header when Expires is not explicitly specified.
if (cookie.getExpires() == null) {
if (cookie.getMaxAge() == 0) {
Date expires = new Date();
expires.setTime(0);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
} else if (cookie.getMaxAge() > 0) {
Date expires = new Date();
expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
}
}
}
if (cookie.getExpires() != null) {
header.append("; Expires=");
header.append(DateUtils.toDateString(cookie.getExpires()));
}
if (cookie.getComment() != null && !cookie.getComment().isEmpty()) {
header.append("; Comment=");
header.append(cookie.getComment());
}
if (cookie.isSameSite()) {
if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
header.append("; SameSite=");
header.append(cookie.getSameSiteMode());
}
}
return header.toString();
}
private static String addVersion0ResponseCookieToExchange(final Cookie cookie) {
final StringBuilder header = new StringBuilder(cookie.getName());
header.append("=");
if (cookie.getValue() != null) {
LegacyCookieSupport.maybeQuote(header, cookie.getValue());
}
if (cookie.getPath() != null) {
header.append("; path=");
LegacyCookieSupport.maybeQuote(header, cookie.getPath());
}
if (cookie.getDomain() != null) {
header.append("; domain=");
LegacyCookieSupport.maybeQuote(header, cookie.getDomain());
}
if (cookie.isSecure()) {
header.append("; secure");
}
if (cookie.isHttpOnly()) {
header.append("; HttpOnly");
}
if (cookie.getExpires() != null) {
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(cookie.getExpires()));
} else if (cookie.getMaxAge() != null) {
if (cookie.getMaxAge() >= 0) {
header.append("; Max-Age=");
header.append(cookie.getMaxAge());
}
if (cookie.getMaxAge() == 0) {
Date expires = new Date();
expires.setTime(0);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
} else if (cookie.getMaxAge() > 0) {
Date expires = new Date();
expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
}
}
if (cookie.isSameSite()) {
if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
header.append("; SameSite=");
header.append(cookie.getSameSiteMode());
}
}
return header.toString();
}
private static String addVersion1ResponseCookieToExchange(final Cookie cookie) {
final StringBuilder header = new StringBuilder(cookie.getName());
header.append("=");
if (cookie.getValue() != null) {
LegacyCookieSupport.maybeQuote(header, cookie.getValue());
}
header.append("; Version=1");
if (cookie.getPath() != null) {
header.append("; Path=");
LegacyCookieSupport.maybeQuote(header, cookie.getPath());
}
if (cookie.getDomain() != null) {
header.append("; Domain=");
LegacyCookieSupport.maybeQuote(header, cookie.getDomain());
}
if (cookie.isDiscard()) {
header.append("; Discard");
}
if (cookie.isSecure()) {
header.append("; Secure");
}
if (cookie.isHttpOnly()) {
header.append("; HttpOnly");
}
if (cookie.getMaxAge() != null) {
if (cookie.getMaxAge() >= 0) {
header.append("; Max-Age=");
header.append(cookie.getMaxAge());
}
// Microsoft IE and Microsoft Edge don't understand Max-Age so send
// expires as well. Without this, persistent cookies fail with those
// browsers. They do understand Expires, even with V1 cookies.
// So, we add Expires header when Expires is not explicitly specified.
if (cookie.getExpires() == null) {
if (cookie.getMaxAge() == 0) {
Date expires = new Date();
expires.setTime(0);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
} else if (cookie.getMaxAge() > 0) {
Date expires = new Date();
expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
header.append("; Expires=");
header.append(DateUtils.toOldCookieDateString(expires));
}
}
}
if (cookie.getExpires() != null) {
header.append("; Expires=");
header.append(DateUtils.toDateString(cookie.getExpires()));
}
if (cookie.getComment() != null && !cookie.getComment().isEmpty()) {
header.append("; Comment=");
LegacyCookieSupport.maybeQuote(header, cookie.getComment());
}
if (cookie.isSameSite()) {
if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
header.append("; SameSite=");
header.append(cookie.getSameSiteMode());
}
}
return header.toString();
}
public static void executeRootHandler(final HttpHandler handler, final HttpServerExchange exchange) {
try {
exchange.beginExecutingHandlerChain();
handler.handleRequest(exchange);
exchange.endExecutingHandlerChain();
boolean resumed = exchange.delegate.isIoOperationQueued();
if (exchange.isDispatched()) {
if (resumed) {
UndertowLogger.REQUEST_LOGGER.resumedAndDispatched();
exchange.setStatusCode(500);
exchange.endExchange();
return;
}
final Runnable dispatchTask = exchange.getDispatchTask();
Executor executor = exchange.getDispatchExecutor();
exchange.setDispatchExecutor(null);
exchange.unDispatch();
if (dispatchTask != null) {
executor = executor == null ? exchange.getWorker() : executor;
try {
executor.execute(dispatchTask);
} catch (RejectedExecutionException e) {
UndertowLogger.REQUEST_LOGGER.debug("Failed to dispatch to worker", e);
exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
exchange.endExchange();
}
}
} else if (!resumed) {
exchange.endExchange();
} else {
exchange.runResumeReadWrite();
}
} catch (Throwable t) {
exchange.putAttachment(DefaultResponseListener.EXCEPTION, t);
exchange.endExecutingHandlerChain();
if (!exchange.isResponseStarted()) {
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
}
if (t instanceof IOException) {
UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t);
} else {
UndertowLogger.REQUEST_LOGGER.undertowRequestFailed(t, exchange);
}
exchange.endExchange();
}
}
/**
* Sets the request path and query parameters, decoding to the requested charset.
*
* @param exchange The exchange
* @param encodedPath The encoded path
* @param charset The charset
*/
@Deprecated
public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer) {
try {
setExchangeRequestPath(exchange, encodedPath, charset, decode, allowEncodedSlash, decodeBuffer, exchange.getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS));
} catch (ParameterLimitException e) {
throw new RuntimeException(e);
}
}
/**
* Sets the request path and query parameters, decoding to the requested charset.
*
* @param exchange The exchange
* @param encodedPath The encoded path
* @param charset The charset
*/
public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException {
boolean requiresDecode = false;
final StringBuilder pathBuilder = new StringBuilder();
int currentPathPartIndex = 0;
for (int i = 0; i < encodedPath.length(); ++i) {
char c = encodedPath.charAt(i);
if (c == '?') {
String part;
String encodedPart = encodedPath.substring(currentPathPartIndex, i);
if (requiresDecode) {
part = URLUtils.decode(encodedPart, charset, allowEncodedSlash,false, decodeBuffer);
} else {
part = encodedPart;
}
pathBuilder.append(part);
part = pathBuilder.toString();
exchange.setRequestPath(part);
exchange.setRelativePath(part);
exchange.setRequestURI(encodedPath.substring(0, i));
final String qs = encodedPath.substring(i + 1);
exchange.setQueryString(qs);
URLUtils.parseQueryString(qs, exchange, charset, decode, maxParameters);
return;
} else if(c == ';') {
String part;
String encodedPart = encodedPath.substring(currentPathPartIndex, i);
if (requiresDecode) {
part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer);
} else {
part = encodedPart;
}
pathBuilder.append(part);
exchange.setRequestURI(encodedPath);
currentPathPartIndex = i + 1 + URLUtils.parsePathParams(encodedPath.substring(i + 1), exchange, charset, decode, maxParameters);
i = currentPathPartIndex -1 ;
} else if(c == '%' || c == '+') {
requiresDecode = decode;
}
}
String part;
String encodedPart = encodedPath.substring(currentPathPartIndex);
if (requiresDecode) {
part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer);
} else {
part = encodedPart;
}
pathBuilder.append(part);
part = pathBuilder.toString();
exchange.setRequestPath(part);
exchange.setRelativePath(part);
exchange.setRequestURI(encodedPath);
}
}