Skip to content

Commit

Permalink
Improve documentation for ambiguous URIs (#12554)
Browse files Browse the repository at this point in the history
* Improve documentation for ambiguous URIs
  • Loading branch information
gregw authored Nov 28, 2024
1 parent c14c391 commit 664d267
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ If you want to customize the violations that you want to allow, you can create y
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java[tags=uriComplianceCustom]
----

[[servleturi]]
=== Servlet URI Compliance Modes

Even if the server has been configured (as above) to allow ambiguous URIs to be received, individual Servlet contexts may not allow such ambiguous URIs to be returned via some specific methods.

Specifically the `HttpServletRequest` methods: link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getServletPath()[getServletPath()] and link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getPathInfo()[getPathInfo()], may throw `IllegalArgumentException` for such URIs.

The intention is for safer methods, such as link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getRequestURI()[getRequestURI] to be used instead.

If necessary, the `ServletHandler` can be configured to allow ambiguous URIs from all methods with link:{javadoc-url}/org/eclipse/jetty/ee10/servlet/ServletHandler.html#setDecodeAmbiguousURIs(boolean)[setDecodeAmbiguousURIs(boolean)].

[[cookie]]
== Cookie Compliance Modes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ Configure Commands:
Options:
--------

--env=<environmentName>
Sets the environment which will apply to all subsequent libraries,
properties and XML files.

--modules=<moduleName>(,<moduleName>)*
Enables a module for this execution.
To enable a module for all future executions, use the
Expand All @@ -145,7 +149,8 @@ Options:

--libs=<classpath>
Adds the specified class-path entries to the the server
class-path (or module-path).
class-path; or module-path; or environment classpath if
there has been a prior --env argument.

--files=<uri>|<location>
--download=<uri>|<location>
Expand All @@ -168,7 +173,6 @@ Options:
and any `--include-jetty-dir` and finally
checking the `${jetty.home}`)


--exec
Executes the generated command line in a forked JVM
(see the --dry-run command).
Expand Down Expand Up @@ -234,6 +238,42 @@ Options:
configuration to all specific ${jetty.base} directories
without having to modify ${jetty.home}.

-D<name>=<value>
Specifies a system property, as well as a start property.
Note: this is a program argument that is interpreted and
added to the existing JVM system properties.

<file>.xml
Specifies a Jetty XML file relative to ${jetty.base}.
This file is in addition to the Jetty XML files resolved
from the [xml] sections of the enabled modules.
If there has been a prior --env argument, the XML will be
executed within that environment

<name>=<value>
Specifies a property value that overrides the same
property defined in a ${jetty.base}/start.d/*.ini file,
or in the [ini] section of a *.mod file.

<name>=<value>
Sets the property value unconditionally.
<name>+=<value>
Appends the given value to the existing value.
<name>?=<value>
Sets the property value only if it is not already set.

If there has been a prior --env argument, then the property is
set only for the current environment, otherwise it is set for
the whole server.

<file>.properties
Specifies a file of property assignments that overrides the same
property defined in a ${jetty.base}/start.d/*.ini file,
or in the [ini] section of a *.mod file.
If there has been a prior --env argument, then the property is
set only for the current environment, otherwise it is set for
the whole server.

jetty.home=<directory>
Sets the ${jetty.home} directory.
By default it is resolved from the start.jar file path.
Expand Down Expand Up @@ -272,26 +312,4 @@ Options:

maven.repo.uri=<url>
The base URL to use to download Maven dependencies.
Defaults to: https://repo1.maven.org/maven2/.

<name>=<value>
Specifies a property value that overrides the same
property defined in a ${jetty.base}/start.d/*.ini file,
or in the [ini] section of a *.mod file.

<name>=<value>
Sets the property value unconditionally.
<name>+=<value>
Appends the given value to the existing value.
<name>?=<value>
Sets the property value only if it is not already set.

-D<name>=<value>
Specifies a system property, as well as a start property.
Note: this is a program argument that is interpreted and
added to the existing JVM system properties.

<xml-file>
Specifies a Jetty XML file relative to ${jetty.base}.
This file is in addition to the Jetty XML files resolved
from the [xml] sections of the enabled modules.
Defaults to: https://repo1.maven.org/maven2/.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import jakarta.servlet.ServletInputStream;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
Expand Down Expand Up @@ -276,9 +277,7 @@ private int read(ByteBuffer buffer, byte[] b, int off, int len) throws IOExcepti
Throwable failure = chunk.getFailure();
if (LOG.isDebugEnabled())
LOG.debug("read failure={} {}", failure, this);
if (failure instanceof IOException)
throw (IOException)failure;
throw new IOException(failure);
throw IO.rethrow(failure);
}

// Empty and not a failure; can only be EOF as per ContentProducer.nextChunk() contract.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.MatchedPath;
import org.eclipse.jetty.http.pathmap.MatchedResource;
Expand Down Expand Up @@ -132,8 +133,12 @@ public boolean isDecodeAmbiguousURIs()
}

/**
* @param decodeAmbiguousURIs {@code True} if ambiguous URIs are decoded by {@link ServletApiRequest#getServletPath()}
* and {@link ServletApiRequest#getPathInfo()}.
* <p>Allow or disallow ambiguous URIs to be returned by {@link ServletApiRequest#getServletPath()}
* and {@link ServletApiRequest#getPathInfo()}.</p>
* <p>Note that the {@link org.eclipse.jetty.server.HttpConfiguration#setUriCompliance(UriCompliance)}
* must also be set to allow ambiguous URIs to be accepted by the {@link org.eclipse.jetty.server.Connector}.</p>
*
* @param decodeAmbiguousURIs {@code True} if ambiguous URIs are decoded by all servlet API methods.
*/
public void setDecodeAmbiguousURIs(boolean decodeAmbiguousURIs)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.AsyncRequestContent;
import org.eclipse.jetty.client.BytesRequestContent;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.client.Result;
import org.eclipse.jetty.client.StringRequestContent;
import org.eclipse.jetty.http.HttpHeader;
Expand All @@ -38,6 +41,7 @@
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.SizeLimitHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -144,6 +148,54 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
assertThat(response.getStatus(), equalTo(HttpStatus.PAYLOAD_TOO_LARGE_413));
}

@Test
public void testChunkedEcho() throws Exception
{
start(new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
String requestContent = IO.toString(req.getInputStream());
resp.getWriter().print(requestContent);
}
});

String content = "x".repeat(SIZE_LIMIT);

URI uri = URI.create("http://localhost:" + _connector.getLocalPort());
Exchanger<Response> exchanger = new Exchanger<>();
AsyncRequestContent asyncRequestContent = new AsyncRequestContent();
_client.POST(uri).body(asyncRequestContent).send(result ->
{
try
{
exchanger.exchange(result.getResponse());
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
});

try (Blocker.Callback callback = Blocker.callback())
{
asyncRequestContent.write(false, BufferUtil.toBuffer(content), callback);
asyncRequestContent.flush();
callback.block();
}
try (Blocker.Callback callback = Blocker.callback())
{
asyncRequestContent.write(true, BufferUtil.toBuffer(content), callback);
asyncRequestContent.flush();
callback.block();
}
asyncRequestContent.close();

Response response = exchanger.exchange(null);
assertThat(response.getStatus(), equalTo(HttpStatus.PAYLOAD_TOO_LARGE_413));
}

@Test
public void testGzipEchoNoAcceptEncoding() throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
Expand Down Expand Up @@ -310,11 +311,7 @@ private int read(ByteBuffer buffer, byte[] b, int off, int len) throws IOExcepti
if (LOG.isDebugEnabled())
LOG.debug("read error={} {}", error, this);
if (error != null)
{
if (error instanceof IOException)
throw (IOException)error;
throw new IOException(error);
}
ExceptionUtil.ifExceptionThrowAs(IOException.class, error);

if (content.isEof())
{
Expand Down

0 comments on commit 664d267

Please sign in to comment.