Skip to content

Commit

Permalink
Implemented servlet 6.1 redirect with content (#11743)
Browse files Browse the repository at this point in the history
* Implemented servlet 6.1 redirect with content

Added option for server to generate a short html redirect body content, as per RFC9110 (default false)
Allowed an aggregated servlet response content to be used if clear is false.

* Redirect is a noop in include

* Fixed init order

This style of extensibility (calling virtuals from constructors) is very fragile.

* Update javadoc from review

Also update EE10 to also noop included response methods

* Update javadoc from review

Also update EE10 to also noop included response methods

* Update javadoc from review

Also update EE10 to also noop included response methods

* Update jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletApiResponse.java

Co-authored-by: Jan Bartel <janb@webtide.com>

* Updating ee11 jsp-impl to 11.0.0-M19

* Updates from review

* updates from review

* Update jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java

Co-authored-by: Simone Bordet <simone.bordet@gmail.com>

---------

Co-authored-by: Jan Bartel <janb@webtide.com>
Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Co-authored-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
4 people authored May 20, 2024
1 parent 22ddb6d commit 4c1c6a2
Show file tree
Hide file tree
Showing 27 changed files with 851 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ private AfterAuthenticationListener(Authentication.Result authenticationResult)
public void onSuccess(Response response)
{
int status = response.getStatus();
if (HttpStatus.isSuccess(status) || HttpStatus.isRedirection(status))
if (HttpStatus.isSuccess(status) || HttpStatus.isRedirectionWithLocation(status))
client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ public static boolean isSuccess(int code)
}

/**
* Simple test against an code to determine if it falls into the
* Simple test against a code to determine if it falls into the
* <code>Redirection</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
Expand All @@ -386,6 +386,22 @@ public static boolean isRedirection(int code)
return ((300 <= code) && (code <= 399));
}

/**
* Simple test against a code to determine if it falls into the
* <code>Redirection</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>; and
* is a code that can requires a location (i.e. not 304).
*
* @param code the code to test.
* @return true if within range of codes that belongs to
* <code>Redirection</code> messages and not a {@code 304}
*/
public static boolean isRedirectionWithLocation(int code)
{
return isRedirection(code) && code != 304;
}

/**
* Simple test against an code to determine if it falls into the
* <code>Client Error</code> message category as defined in the <a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public int getStatusCode()
*/
public void setStatusCode(int statusCode)
{
if (!HttpStatus.isRedirection(statusCode))
if (!HttpStatus.isRedirectionWithLocation(statusCode))
throw new IllegalArgumentException("Invalid redirect status code " + statusCode + " (must be a value between 300 and 399)");
_statusCode = statusCode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public int getStatusCode()

public void setStatusCode(int statusCode)
{
if (!HttpStatus.isRedirection(statusCode))
if (!HttpStatus.isRedirectionWithLocation(statusCode))
throw new IllegalArgumentException("Invalid redirect status code " + statusCode + " (must be a value between 300 and 399)");
_statusCode = statusCode;
}
Expand Down
1 change: 1 addition & 0 deletions jetty-core/jetty-server/src/main/config/etc/jetty.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Set name="requestCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="from"><Arg><Property name="jetty.httpConfig.requestCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="responseCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="from"><Arg><Property name="jetty.httpConfig.responseCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="relativeRedirectAllowed"><Property name="jetty.httpConfig.relativeRedirectAllowed" default="false"/></Set>
<Set name="generateRedirectBody" property="jetty.httpConfig.generateRedirectBody"/>
<Set name="useInputDirectByteBuffers" property="jetty.httpConfig.useInputDirectByteBuffers"/>
<Set name="useOutputDirectByteBuffers" property="jetty.httpConfig.useOutputDirectByteBuffers"/>
</New>
Expand Down
3 changes: 3 additions & 0 deletions jetty-core/jetty-server/src/main/config/modules/server.mod
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ etc/jetty.xml
## Relative Redirect Locations allowed
# jetty.httpConfig.relativeRedirectAllowed=true

## Redirect body generated
# jetty.httpConfig.generateRedirectBody=false

## Whether to use direct ByteBuffers for reading or writing
# jetty.httpConfig.useInputDirectByteBuffers=true
# jetty.httpConfig.useOutputDirectByteBuffers=true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ default boolean isSecure()

/**
* @return whether the functionality of pushing resources is supported
* @deprecated in favour of 103 Early Hints
*/
@Deprecated(since = "12.0.1")
default boolean isPushSupported()
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class HttpConfiguration implements Dumpable
private MultiPartCompliance _multiPartCompliance = MultiPartCompliance.RFC7578;
private boolean _notifyRemoteAsyncErrors = true;
private boolean _relativeRedirectAllowed = true;
private boolean _generateRedirectBody = false;
private HostPort _serverAuthority;
private SocketAddress _localAddress;
private int _maxUnconsumedRequestContentReads = 16;
Expand Down Expand Up @@ -158,6 +159,7 @@ public HttpConfiguration(HttpConfiguration config)
_complianceViolationListeners.addAll(config._complianceViolationListeners);
_notifyRemoteAsyncErrors = config._notifyRemoteAsyncErrors;
_relativeRedirectAllowed = config._relativeRedirectAllowed;
_generateRedirectBody = config._generateRedirectBody;
_uriCompliance = config._uriCompliance;
_serverAuthority = config._serverAuthority;
_localAddress = config._localAddress;
Expand Down Expand Up @@ -716,6 +718,23 @@ public boolean isRelativeRedirectAllowed()
return _relativeRedirectAllowed;
}

/**
* @param generate True if a redirection body will be generated if no response body is supplied.
*/
public void setGenerateRedirectBody(boolean generate)
{
_generateRedirectBody = generate;
}

/**
* @return True if a redirection body will be generated if no response body is supplied.
*/
@ManagedAttribute("Whether a redirection response body will be generated")
public boolean isGenerateRedirectBody()
{
return _generateRedirectBody;
}

/**
* Get the SocketAddress override to be reported as the local address of all connections
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ListIterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
Expand All @@ -32,12 +33,14 @@
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
Expand Down Expand Up @@ -262,10 +265,7 @@ static void sendRedirect(Request request, Response response, Callback callback,
*/
static void sendRedirect(Request request, Response response, Callback callback, String location, boolean consumeAvailable)
{
int code = HttpMethod.GET.is(request.getMethod()) || request.getConnectionMetaData().getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()
? HttpStatus.MOVED_TEMPORARILY_302
: HttpStatus.SEE_OTHER_303;
sendRedirect(request, response, callback, code, location, consumeAvailable);
sendRedirect(request, response, callback, 0, location, consumeAvailable);
}

/**
Expand All @@ -283,23 +283,47 @@ static void sendRedirect(Request request, Response response, Callback callback,
*/
static void sendRedirect(Request request, Response response, Callback callback, int code, String location, boolean consumeAvailable)
{
if (!HttpStatus.isRedirection(code))
sendRedirect(request, response, callback, code, location, consumeAvailable, null);
}

/**
* <p>Sends a {@code 302} HTTP redirect status code to the given location.</p>
*
* @param request the HTTP request
* @param response the HTTP response
* @param callback the callback to complete
* @param code the redirect HTTP status code, or 0 for a default
* @param location the redirect location as an absolute URI or encoded relative URI path.
* @param consumeAvailable whether to consumer the available request content
* @param content the content of the response, or null for a generated HTML message if {@link HttpConfiguration#isGenerateRedirectBody()} is {@code true}.
* @see #toRedirectURI(Request, String)
* @throws IllegalArgumentException if the status code is not a redirect, or the location is {@code null}
* @throws IllegalStateException if the response is already {@link #isCommitted() committed}
*/
static void sendRedirect(Request request, Response response, Callback callback, int code, String location, boolean consumeAvailable, ByteBuffer content)
{
if (response.isCommitted())
{
callback.failed(new IllegalArgumentException("Not a 3xx redirect code"));
callback.failed(new IllegalStateException("Committed"));
return;
}

if (location == null)
if (code <= 0)
code = HttpMethod.GET.is(request.getMethod()) || request.getConnectionMetaData().getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()
? HttpStatus.MOVED_TEMPORARILY_302
: HttpStatus.SEE_OTHER_303;
if (!HttpStatus.isRedirectionWithLocation(code))
{
callback.failed(new IllegalArgumentException("No location"));
callback.failed(new IllegalArgumentException("Not a 3xx redirect code"));
return;
}

if (response.isCommitted())
if (location == null)
{
callback.failed(new IllegalStateException("Committed"));
callback.failed(new IllegalArgumentException("No location"));
return;
}
location = toRedirectURI(request, location);

if (consumeAvailable)
{
Expand All @@ -317,9 +341,22 @@ static void sendRedirect(Request request, Response response, Callback callback,
}
}

response.getHeaders().put(HttpHeader.LOCATION, toRedirectURI(request, location));
if (content == null && request.getConnectionMetaData().getHttpConfiguration().isGenerateRedirectBody())
{
response.getHeaders().put(MimeTypes.Type.TEXT_HTML_8859_1.getContentTypeField());
String body = """
<!DOCTYPE html>
<html lang="en">
<head><meta charset="ISO-8859-1"/><meta http-equiv="refresh" content="0; URL=%s"/><title>Redirecting...</title></head>
<body><p>If you are not redirected, <a href="%s">click here</a>.</p></body>
</html>
""".formatted(location, location);
content = BufferUtil.toBuffer(body, StandardCharsets.ISO_8859_1);
}

response.getHeaders().put(HttpHeader.LOCATION, location);
response.setStatus(code);
response.write(true, null, callback);
response.write(true, content, callback);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protected boolean shouldBuffer(Response response, boolean last)
return false;

int status = response.getStatus();
if (HttpStatus.hasNoBody(status) || HttpStatus.isRedirection(status))
if (HttpStatus.hasNoBody(status) || HttpStatus.isRedirectionWithLocation(status))
return false;

String ct = response.getHeaders().get(HttpHeader.CONTENT_TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public int getStatusCode()
*/
public void setStatusCode(int statusCode)
{
if (!HttpStatus.isRedirection(statusCode))
if (!HttpStatus.isRedirectionWithLocation(statusCode))
throw new IllegalArgumentException("Invalid HTTP redirection status code: " + statusCode);
_statusCode = statusCode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public SecuredRedirectHandler(Handler handler)
public SecuredRedirectHandler(Handler handler, int code)
{
super(handler);
if (!HttpStatus.isRedirection(code))
if (!HttpStatus.isRedirectionWithLocation(code))
throw new IllegalArgumentException("Not a 3xx redirect code");
_redirectCode = code;
}
Expand Down
Loading

0 comments on commit 4c1c6a2

Please sign in to comment.