All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.server.Connectors Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * 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 io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.UndertowOptions;
import io.undertow.server.handlers.Cookie;
import io.undertow.server.protocol.http.HttpRequestParser;
import io.undertow.util.BadRequestException;
import io.undertow.util.DateUtils;
import io.undertow.util.HeaderMap;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.LegacyCookieSupport;
import io.undertow.util.ParameterLimitException;
import io.undertow.util.StatusCodes;
import io.undertow.util.URLUtils;
import io.undertow.connector.PooledByteBuffer;
import org.xnio.OptionMap;
import org.xnio.channels.StreamSourceChannel;
import org.xnio.conduits.ConduitStreamSinkChannel;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;

/**
 * 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 * @author Richard Opalka */ public class Connectors { private static final boolean[] ALLOWED_TOKEN_CHARACTERS = new boolean[256]; private static final boolean[] ALLOWED_SCHEME_CHARACTERS = new boolean[256]; static { for(int i = 0; i < ALLOWED_TOKEN_CHARACTERS.length; ++i) { if((i >='0' && i <= '9') || (i >='a' && i <= 'z') || (i >='A' && i <= 'Z')) { ALLOWED_TOKEN_CHARACTERS[i] = true; } else { switch (i) { case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~': { ALLOWED_TOKEN_CHARACTERS[i] = true; break; } default: ALLOWED_TOKEN_CHARACTERS[i] = false; } } } for(int i = 0; i < ALLOWED_SCHEME_CHARACTERS.length; ++i) { if((i >='0' && i <= '9') || (i >='a' && i <= 'z') || (i >='A' && i <= 'Z')) { ALLOWED_SCHEME_CHARACTERS[i] = true; } else { switch (i) { case '+': case '-': case '.': { ALLOWED_SCHEME_CHARACTERS[i] = true; break; } default: ALLOWED_SCHEME_CHARACTERS[i] = false; } } } } /** * 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) { boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION); for (Cookie cookie : exchange.responseCookies()) { exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(cookie, enableRfc6265Validation)); } } /** * Adds the cookie into the response header map. This should be called * before the response is started. * * @param exchange The server exchange * @param cookie The cookie */ public static void addCookie(final HttpServerExchange exchange, Cookie cookie) { boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION); exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(cookie, enableRfc6265Validation)); } /** * 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 buffers The buffers to attach */ public static void ungetRequestBytes(final HttpServerExchange exchange, PooledByteBuffer... buffers) { PooledByteBuffer[] existing = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA); PooledByteBuffer[] newArray; if (existing == null) { newArray = new PooledByteBuffer[buffers.length]; System.arraycopy(buffers, 0, newArray, 0, buffers.length); } else { newArray = new PooledByteBuffer[existing.length + buffers.length]; // If there are previous buffers we are re-buffering data so although // counterintuitive first put the new data and then the existing buffers. // Example: there are buffered data with buffers A,B and A is retrieved // but returned, it should be A,B again and not B,A System.arraycopy(buffers, 0, newArray, 0, buffers.length); System.arraycopy(existing, 0, newArray, buffers.length, existing.length); } exchange.putAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA, newArray); //todo: force some kind of wakeup? exchange.addExchangeCompleteListener(BufferedRequestDataCleanupListener.INSTANCE); } private enum BufferedRequestDataCleanupListener implements ExchangeCompletionListener { INSTANCE; @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { PooledByteBuffer[] bufs = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA); if (bufs != null) { for (PooledByteBuffer i : bufs) { if(i != null) { i.close(); } } } nextListener.proceed(); } } public static void terminateRequest(final HttpServerExchange exchange) { exchange.terminateRequest(); } public static void terminateResponse(final HttpServerExchange exchange) { exchange.terminateResponse(); } public static void resetRequestChannel(final HttpServerExchange 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.setInCall(true); handler.handleRequest(exchange); exchange.setInCall(false); boolean resumed = exchange.isResumed(); 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.getConnection().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.setInCall(false); 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 { final boolean slashDecodingFlag = URLUtils.getSlashDecodingFlag(allowEncodedSlash, exchange.getConnection().getUndertowOptions().get(UndertowOptions.DECODE_SLASH)); setExchangeRequestPath(exchange, encodedPath, charset, decode, slashDecodingFlag, decodeBuffer, exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS)); } catch (ParameterLimitException | BadRequestException e) { throw new RuntimeException(e); } } /** * Sets the request path and query parameters, decoding to the requested charset. * All the options are retrieved from the exchange undertow options. * * @param exchange The exchange * @param encodedPath The encoded path to decode * @param decodeBuffer The decode buffer to use * @throws ParameterLimitException * @throws BadRequestException */ public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, StringBuilder decodeBuffer) throws ParameterLimitException, BadRequestException { final OptionMap options = exchange.getConnection().getUndertowOptions(); boolean slashDecodingFlag = URLUtils.getSlashDecodingFlag(options); setExchangeRequestPath(exchange, encodedPath, options.get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()), options.get(UndertowOptions.DECODE_URL, true), slashDecodingFlag, decodeBuffer, options.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS)); } /** * Sets the request path and query parameters, decoding to the requested charset. * * @param exchange the exchange * @param encodedPath the encoded path * @param decode indicates if the request path should be decoded * @param decodeSlashFlag indicates if slash characters contained in the encoded path should be decoded * @param decodeBuffer the buffer used for decoding * @param maxParameters maximum number of parameters allowed in the path * @param charset the charset * @throws BadRequestException if there is something wrong with the request, such as non-allowed characters */ public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean decodeSlashFlag, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException, BadRequestException { setExchangeRequestPath(exchange, encodedPath, charset, decode, decode, decodeSlashFlag, decodeBuffer, maxParameters); } /** * Sets the request path and query parameters, decoding to the requested charset. * * @param exchange the exchange * @param encodedPath the encoded path * @param decode indicates if the request path should be decoded, apart from the query string part of the * request (see next parameter) * @param decodeQueryString indicates if the query string of the path, when present, should be decoded * @param decodeSlashFlag indicates if slash characters contained in the request path should be decoded * @param decodeBuffer the buffer used for decoding * @param maxParameters maximum number of parameters allowed in the path * @param charset the charset * @throws BadRequestException if there is something wrong with the request, such as non-allowed characters */ public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, boolean decodeQueryString, final boolean decodeSlashFlag, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException, BadRequestException { final OptionMap options = exchange.getConnection().getUndertowOptions(); final boolean allowUnescapedCharactersInUrl = options.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false); 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(!allowUnescapedCharactersInUrl && !HttpRequestParser.isTargetCharacterAllowed(c)) { throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(c)); } if (c == '?') { String part; String encodedPart = encodedPath.substring(currentPathPartIndex, i); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, decodeSlashFlag,false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); part = pathBuilder.toString(); exchange.setRequestPath(part); exchange.setRelativePath(part); if(requiresDecode && allowUnescapedCharactersInUrl) { final String uri = URLUtils.decode(encodedPath.substring(0, i), charset, decodeSlashFlag,false, decodeBuffer); exchange.setRequestURI(uri); } else { exchange.setRequestURI(encodedPath.substring(0, i)); } final String qs = encodedPath.substring(i + 1); if(requiresDecode && allowUnescapedCharactersInUrl) { final String decodedQS = URLUtils.decode(qs, charset, decodeSlashFlag,false, decodeBuffer); exchange.setQueryString(decodedQS); } else { exchange.setQueryString(qs); } URLUtils.parseQueryString(qs, exchange, charset, decodeQueryString, maxParameters); return; } else if(c == ';') { String part; String encodedPart = encodedPath.substring(currentPathPartIndex, i); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, decodeSlashFlag, false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); if(requiresDecode && allowUnescapedCharactersInUrl) { final String uri = URLUtils.decode(encodedPath, charset, decodeSlashFlag,false, decodeBuffer); exchange.setRequestURI(uri); } else { exchange.setRequestURI(encodedPath); } currentPathPartIndex = i + 1 + URLUtils.parsePathParams(encodedPath.substring(i + 1), exchange, charset, decode, maxParameters); i = currentPathPartIndex -1 ; } else if(decode && (c == '+' || c == '%' || c > 127)) { requiresDecode = decode; } } String part; String encodedPart = encodedPath.substring(currentPathPartIndex); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, decodeSlashFlag, false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); part = pathBuilder.toString(); exchange.setRequestPath(part); exchange.setRelativePath(part); exchange.setRequestURI(encodedPath); } /** * Returns the existing request channel, if it exists. Otherwise returns null * * @param exchange The http server exchange */ public static StreamSourceChannel getExistingRequestChannel(final HttpServerExchange exchange) { return exchange.requestChannel; } public static boolean isEntityBodyAllowed(HttpServerExchange exchange){ int code = exchange.getStatusCode(); return isEntityBodyAllowed(code); } public static boolean isEntityBodyAllowed(int code) { if(code >= 100 && code < 200) { return false; } if(code == 204 || code == 304) { return false; } return true; } public static void updateResponseBytesSent(HttpServerExchange exchange, long bytes) { exchange.updateBytesSent(bytes); } public static ConduitStreamSinkChannel getConduitSinkChannel(HttpServerExchange exchange) { return exchange.getConnection().getSinkChannel(); } /** * Verifies that the contents of the HttpString are a valid token according to rfc7230. * @param header The header to verify */ public static void verifyToken(HttpString header) { int length = header.length(); for(int i = 0; i < length; ++i) { byte c = header.byteAt(i); if(!ALLOWED_TOKEN_CHARACTERS[c]) { throw UndertowMessages.MESSAGES.invalidToken(c); } } } /** * Returns true if the token character is valid according to rfc7230 */ public static boolean isValidTokenCharacter(byte c) { return ALLOWED_TOKEN_CHARACTERS[c]; } public static boolean isValidSchemeCharacter(byte c) { return ALLOWED_SCHEME_CHARACTERS[c]; } /** * Verifies that the provided request headers are valid according to rfc7230. In particular: * - At most one content-length or transfer encoding */ public static boolean areRequestHeadersValid(HeaderMap headers) { HeaderValues te = headers.get(Headers.TRANSFER_ENCODING); HeaderValues cl = headers.get(Headers.CONTENT_LENGTH); if(te != null && cl != null) { return false; } else if(te != null && te.size() > 1) { return false; } else if(cl != null && cl.size() > 1) { return false; } return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy