Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x
This commit is contained in:
commit
c1b6e30471
|
@ -16,4 +16,4 @@
|
||||||
|
|
||||||
Eclipse Jetty is an open source project with a long pedigree of contribution.
|
Eclipse Jetty is an open source project with a long pedigree of contribution.
|
||||||
Starting over 20 years ago, Jetty has had many committers over the years and owes much of its success to the people that make up the community.
|
Starting over 20 years ago, Jetty has had many committers over the years and owes much of its success to the people that make up the community.
|
||||||
There are many ways that you may contribute to Jetty and the goal of this guide is help you get there!
|
There are many ways that you may contribute to Jetty, and the goal of this guide is help you get there!
|
||||||
|
|
|
@ -55,7 +55,7 @@ Build related artifacts that release separately, common assembly descriptors, re
|
||||||
Files associated with the development of Jetty -- code styles, formatting, iplogs, etc.::
|
Files associated with the development of Jetty -- code styles, formatting, iplogs, etc.::
|
||||||
http://git.eclipse.org/c/jetty/org.eclipse.jetty.admin.git
|
http://git.eclipse.org/c/jetty/org.eclipse.jetty.admin.git
|
||||||
|
|
||||||
[[t-contributing-build]]
|
[[cg-contributing-build]]
|
||||||
=== Maven Build
|
=== Maven Build
|
||||||
|
|
||||||
Eclipse Jetty uses http://maven.apache.org/[Apache Maven] for managing the project metadata and controlling the build.
|
Eclipse Jetty uses http://maven.apache.org/[Apache Maven] for managing the project metadata and controlling the build.
|
||||||
|
@ -76,7 +76,7 @@ All relevant dependencies should be downloaded into your local repository automa
|
||||||
|
|
||||||
____
|
____
|
||||||
[NOTE]
|
[NOTE]
|
||||||
Jetty has a great many test cases that run through the course of its build. Many of these tests spin up embedded instances of Jetty itself and it is not uncommon to see hundreds or more instances of Jetty start and stop during tests.
|
Jetty has a great many test cases that run through the course of its build. Many of these tests spin up embedded instances of Jetty itself, and it is not uncommon to see hundreds or more instances of Jetty start and stop during tests.
|
||||||
Periodically we find some test cases to be more time dependent than they should be and this results in intermittent test failures.
|
Periodically we find some test cases to be more time dependent than they should be and this results in intermittent test failures.
|
||||||
You can help track these down by opening an https://github.com/eclipse/jetty.project/issues[Issue].
|
You can help track these down by opening an https://github.com/eclipse/jetty.project/issues[Issue].
|
||||||
____
|
____
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
== Participate in the Documentation
|
== Participate in the Documentation
|
||||||
|
|
||||||
Another wonderful way to help with Eclipse Jetty is to help contribute to our corpus of documentation.
|
Another wonderful way to help with Eclipse Jetty is to help contribute to our corpus of documentation.
|
||||||
We have taken every every to reduce the barriers to contributing to our documentation and many contributors find our documentation as a low key way to participate and learn the process.
|
We have made an effort to reduce the barriers to contributing to our documentation, and many contributors find our documentation as a low-key way to participate and learn the process.
|
||||||
|
|
||||||
|
|
||||||
[[cg-documentation-format]]
|
[[cg-documentation-format]]
|
||||||
=== Source Control and Maven Build.
|
=== Source Control and Maven Build.
|
||||||
|
|
||||||
The Jetty documentation is a module within the overall Jetty project and is build as a part of the standard build process.
|
The Jetty documentation is a module within the overall Jetty project and is build as a part of the standard build process.
|
||||||
As such to checkout the documentation you can follow the link:#t-community-source[same process] as checking out Jetty itself.
|
As such to checkout the documentation you can follow the link:#cg-community-source[same process] as checking out Jetty itself.
|
||||||
|
|
||||||
As a part of the main Jetty project the documentation is build through the link:#t-community-build[same process] as Jetty.
|
As a part of the main Jetty project the documentation is build through the link:#cg-contributing-build[same process] as Jetty.
|
||||||
However, it is a more independent module and can be worked with much simpler by building strictly the jetty-documentation module.
|
However, it is a more independent module and can be worked with much simpler by building strictly the jetty-documentation module.
|
||||||
|
|
||||||
[source, screen, subs="{sub-order}"]
|
[source, screen, subs="{sub-order}"]
|
||||||
|
|
|
@ -15,9 +15,8 @@
|
||||||
=== Community
|
=== Community
|
||||||
|
|
||||||
Developers and users alike are welcome to engage the Jetty community.
|
Developers and users alike are welcome to engage the Jetty community.
|
||||||
We all have day jobs here so don't just ask questions and get frustrated if
|
We all have day jobs here so don't just ask questions and get frustrated if no one answers right away.
|
||||||
no one answers right away. Stick around to hear an answer, one is likely coming
|
Stick around to hear an answer, one is likely coming at some point!
|
||||||
at some point!
|
|
||||||
|
|
||||||
[[community-mailing-lists]]
|
[[community-mailing-lists]]
|
||||||
==== Mailing Lists
|
==== Mailing Lists
|
||||||
|
|
|
@ -14,7 +14,7 @@ logging
|
||||||
commons-logging
|
commons-logging
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
maven://org.slf4j/jcl-over-slf4j/${slf4j.version}|jcl-over-slf4j-${slf4j.version}.jar
|
maven://org.slf4j/jcl-over-slf4j/${slf4j.version}|lib/logging/jcl-over-slf4j-${slf4j.version}.jar
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
lib/logging/jcl-over-slf4j-${slf4j.version}.jar
|
lib/logging/jcl-over-slf4j-${slf4j.version}.jar
|
||||||
|
|
|
@ -14,10 +14,10 @@ logging
|
||||||
log4j
|
log4j
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
maven://org.slf4j/jcl-over-slf4j/${slf4j.version}|jcl-over-slf4j-${slf4j.version}.jar
|
maven://org.slf4j/log4j-over-slf4j/${slf4j.version}|lib/logging/log4j-over-slf4j-${slf4j.version}.jar
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
lib/logging/log4j-to-slf4j-${slf4j.version}.jar
|
lib/logging/log4j-over-slf4j-${slf4j.version}.jar
|
||||||
|
|
||||||
[license]
|
[license]
|
||||||
SLF4J is distributed under the MIT License.
|
SLF4J is distributed under the MIT License.
|
||||||
|
|
|
@ -48,7 +48,7 @@ import org.eclipse.jetty.util.UrlEncoded;
|
||||||
*/
|
*/
|
||||||
public interface HttpURI
|
public interface HttpURI
|
||||||
{
|
{
|
||||||
enum Ambiguous
|
enum Violation
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* URI contains ambiguous path segments e.g. {@code /foo/%2e%2e/bar}
|
* URI contains ambiguous path segments e.g. {@code /foo/%2e%2e/bar}
|
||||||
|
@ -73,7 +73,12 @@ public interface HttpURI
|
||||||
/**
|
/**
|
||||||
* URI contains ambiguous path parameters within a URI segment e.g. {@code /foo/..;/bar}
|
* URI contains ambiguous path parameters within a URI segment e.g. {@code /foo/..;/bar}
|
||||||
*/
|
*/
|
||||||
PARAM
|
PARAM,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains UTF16 encodings
|
||||||
|
*/
|
||||||
|
UTF16
|
||||||
}
|
}
|
||||||
|
|
||||||
static Mutable build()
|
static Mutable build()
|
||||||
|
@ -165,6 +170,11 @@ public interface HttpURI
|
||||||
*/
|
*/
|
||||||
boolean isAmbiguous();
|
boolean isAmbiguous();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if the URI has any Violations.
|
||||||
|
*/
|
||||||
|
boolean hasViolations();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return True if the URI has a possibly ambiguous segment like '..;' or '%2e%2e'
|
* @return True if the URI has a possibly ambiguous segment like '..;' or '%2e%2e'
|
||||||
*/
|
*/
|
||||||
|
@ -190,6 +200,8 @@ public interface HttpURI
|
||||||
*/
|
*/
|
||||||
boolean hasAmbiguousEncoding();
|
boolean hasAmbiguousEncoding();
|
||||||
|
|
||||||
|
boolean hasUtf16Encoding();
|
||||||
|
|
||||||
default URI toURI()
|
default URI toURI()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -215,7 +227,7 @@ public interface HttpURI
|
||||||
private final String _fragment;
|
private final String _fragment;
|
||||||
private String _uri;
|
private String _uri;
|
||||||
private String _decodedPath;
|
private String _decodedPath;
|
||||||
private final EnumSet<Mutable.Ambiguous> _ambiguous = EnumSet.noneOf(Mutable.Ambiguous.class);
|
private final EnumSet<Violation> _violations = EnumSet.noneOf(Violation.class);
|
||||||
|
|
||||||
private Immutable(Mutable builder)
|
private Immutable(Mutable builder)
|
||||||
{
|
{
|
||||||
|
@ -229,7 +241,7 @@ public interface HttpURI
|
||||||
_fragment = builder._fragment;
|
_fragment = builder._fragment;
|
||||||
_uri = builder._uri;
|
_uri = builder._uri;
|
||||||
_decodedPath = builder._decodedPath;
|
_decodedPath = builder._decodedPath;
|
||||||
_ambiguous.addAll(builder._ambiguous);
|
_violations.addAll(builder._violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Immutable(String uri)
|
private Immutable(String uri)
|
||||||
|
@ -396,37 +408,49 @@ public interface HttpURI
|
||||||
@Override
|
@Override
|
||||||
public boolean isAmbiguous()
|
public boolean isAmbiguous()
|
||||||
{
|
{
|
||||||
return !_ambiguous.isEmpty();
|
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasViolations()
|
||||||
|
{
|
||||||
|
return !_violations.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousSegment()
|
public boolean hasAmbiguousSegment()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.SEGMENT);
|
return _violations.contains(Violation.SEGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousEmptySegment()
|
public boolean hasAmbiguousEmptySegment()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.EMPTY);
|
return _violations.contains(Violation.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousSeparator()
|
public boolean hasAmbiguousSeparator()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.SEPARATOR);
|
return _violations.contains(Violation.SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousParameter()
|
public boolean hasAmbiguousParameter()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.PARAM);
|
return _violations.contains(Violation.PARAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousEncoding()
|
public boolean hasAmbiguousEncoding()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.ENCODING);
|
return _violations.contains(Violation.ENCODING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasUtf16Encoding()
|
||||||
|
{
|
||||||
|
return _violations.contains(Violation.UTF16);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -480,12 +504,18 @@ public interface HttpURI
|
||||||
*/
|
*/
|
||||||
private static final Index<Boolean> __ambiguousSegments = new Index.Builder<Boolean>()
|
private static final Index<Boolean> __ambiguousSegments = new Index.Builder<Boolean>()
|
||||||
.caseSensitive(false)
|
.caseSensitive(false)
|
||||||
.with("%2e", Boolean.TRUE)
|
|
||||||
.with("%2e%2e", Boolean.TRUE)
|
|
||||||
.with(".%2e", Boolean.TRUE)
|
|
||||||
.with("%2e.", Boolean.TRUE)
|
|
||||||
.with("..", Boolean.FALSE)
|
|
||||||
.with(".", Boolean.FALSE)
|
.with(".", Boolean.FALSE)
|
||||||
|
.with("%2e", Boolean.TRUE)
|
||||||
|
.with("%u002e", Boolean.TRUE)
|
||||||
|
.with("..", Boolean.FALSE)
|
||||||
|
.with(".%2e", Boolean.TRUE)
|
||||||
|
.with(".%u002e", Boolean.TRUE)
|
||||||
|
.with("%2e.", Boolean.TRUE)
|
||||||
|
.with("%2e%2e", Boolean.TRUE)
|
||||||
|
.with("%2e%u002e", Boolean.TRUE)
|
||||||
|
.with("%u002e.", Boolean.TRUE)
|
||||||
|
.with("%u002e%2e", Boolean.TRUE)
|
||||||
|
.with("%u002e%u002e", Boolean.TRUE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private String _scheme;
|
private String _scheme;
|
||||||
|
@ -498,7 +528,7 @@ public interface HttpURI
|
||||||
private String _fragment;
|
private String _fragment;
|
||||||
private String _uri;
|
private String _uri;
|
||||||
private String _decodedPath;
|
private String _decodedPath;
|
||||||
private final EnumSet<Ambiguous> _ambiguous = EnumSet.noneOf(Ambiguous.class);
|
private final EnumSet<Violation> _violations = EnumSet.noneOf(Violation.class);
|
||||||
private boolean _emptySegment;
|
private boolean _emptySegment;
|
||||||
|
|
||||||
private Mutable()
|
private Mutable()
|
||||||
|
@ -623,7 +653,7 @@ public interface HttpURI
|
||||||
_fragment = null;
|
_fragment = null;
|
||||||
_uri = null;
|
_uri = null;
|
||||||
_decodedPath = null;
|
_decodedPath = null;
|
||||||
_ambiguous.clear();
|
_violations.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,37 +780,49 @@ public interface HttpURI
|
||||||
@Override
|
@Override
|
||||||
public boolean isAmbiguous()
|
public boolean isAmbiguous()
|
||||||
{
|
{
|
||||||
return !_ambiguous.isEmpty();
|
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasViolations()
|
||||||
|
{
|
||||||
|
return !_violations.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousSegment()
|
public boolean hasAmbiguousSegment()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Mutable.Ambiguous.SEGMENT);
|
return _violations.contains(Violation.SEGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousEmptySegment()
|
public boolean hasAmbiguousEmptySegment()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.EMPTY);
|
return _violations.contains(Violation.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousSeparator()
|
public boolean hasAmbiguousSeparator()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Mutable.Ambiguous.SEPARATOR);
|
return _violations.contains(Violation.SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousParameter()
|
public boolean hasAmbiguousParameter()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.PARAM);
|
return _violations.contains(Violation.PARAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAmbiguousEncoding()
|
public boolean hasAmbiguousEncoding()
|
||||||
{
|
{
|
||||||
return _ambiguous.contains(Ambiguous.ENCODING);
|
return _violations.contains(Violation.ENCODING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasUtf16Encoding()
|
||||||
|
{
|
||||||
|
return _violations.contains(Violation.UTF16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mutable normalize()
|
public Mutable normalize()
|
||||||
|
@ -885,9 +927,9 @@ public interface HttpURI
|
||||||
_uri = null;
|
_uri = null;
|
||||||
_decodedPath = uri.getDecodedPath();
|
_decodedPath = uri.getDecodedPath();
|
||||||
if (uri.hasAmbiguousSeparator())
|
if (uri.hasAmbiguousSeparator())
|
||||||
_ambiguous.add(Ambiguous.SEPARATOR);
|
_violations.add(Violation.SEPARATOR);
|
||||||
if (uri.hasAmbiguousSegment())
|
if (uri.hasAmbiguousSegment())
|
||||||
_ambiguous.add(Ambiguous.SEGMENT);
|
_violations.add(Violation.SEGMENT);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,9 +980,11 @@ public interface HttpURI
|
||||||
int mark = 0; // the start of the current section being parsed
|
int mark = 0; // the start of the current section being parsed
|
||||||
int pathMark = 0; // the start of the path section
|
int pathMark = 0; // the start of the path section
|
||||||
int segment = 0; // the start of the current segment within the path
|
int segment = 0; // the start of the current segment within the path
|
||||||
boolean encoded = false; // set to true if the path contains % encoded characters
|
boolean encodedPath = false; // set to true if the path contains % encoded characters
|
||||||
boolean dot = false; // set to true if the path containers . or .. segments
|
boolean encodedUtf16 = false; // Is the current encoding for UTF16?
|
||||||
int escapedTwo = 0; // state of parsing a %2<x>
|
int encodedCharacters = 0; // partial state of parsing a % encoded character<x>
|
||||||
|
int encodedValue = 0; // the partial encoded value
|
||||||
|
boolean dot = false; // set to true if the path contains . or .. segments
|
||||||
int end = uri.length();
|
int end = uri.length();
|
||||||
_emptySegment = false;
|
_emptySegment = false;
|
||||||
for (int i = 0; i < end; i++)
|
for (int i = 0; i < end; i++)
|
||||||
|
@ -981,8 +1025,9 @@ public interface HttpURI
|
||||||
state = State.ASTERISK;
|
state = State.ASTERISK;
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
encoded = true;
|
encodedPath = true;
|
||||||
escapedTwo = 1;
|
encodedCharacters = 2;
|
||||||
|
encodedValue = 0;
|
||||||
mark = pathMark = segment = i;
|
mark = pathMark = segment = i;
|
||||||
state = State.PATH;
|
state = State.PATH;
|
||||||
break;
|
break;
|
||||||
|
@ -1032,9 +1077,10 @@ public interface HttpURI
|
||||||
state = State.QUERY;
|
state = State.QUERY;
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
// must have be in an encoded path
|
// must have been in an encoded path
|
||||||
encoded = true;
|
encodedPath = true;
|
||||||
escapedTwo = 1;
|
encodedCharacters = 2;
|
||||||
|
encodedValue = 0;
|
||||||
state = State.PATH;
|
state = State.PATH;
|
||||||
break;
|
break;
|
||||||
case '#':
|
case '#':
|
||||||
|
@ -1153,6 +1199,34 @@ public interface HttpURI
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PATH:
|
case PATH:
|
||||||
|
{
|
||||||
|
if (encodedCharacters > 0)
|
||||||
|
{
|
||||||
|
if (encodedCharacters == 2 && c == 'u' && !encodedUtf16)
|
||||||
|
{
|
||||||
|
_violations.add(Violation.UTF16);
|
||||||
|
encodedUtf16 = true;
|
||||||
|
encodedCharacters = 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
encodedValue = (encodedValue << 4) + TypeUtil.convertHexDigit(c);
|
||||||
|
|
||||||
|
if (--encodedCharacters == 0)
|
||||||
|
{
|
||||||
|
switch (encodedValue)
|
||||||
|
{
|
||||||
|
case '/':
|
||||||
|
_violations.add(Violation.SEPARATOR);
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
_violations.add(Violation.ENCODING);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
|
@ -1183,27 +1257,15 @@ public interface HttpURI
|
||||||
dot |= segment == i;
|
dot |= segment == i;
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
encoded = true;
|
encodedPath = true;
|
||||||
escapedTwo = 1;
|
encodedUtf16 = false;
|
||||||
break;
|
encodedCharacters = 2;
|
||||||
case '2':
|
encodedValue = 0;
|
||||||
escapedTwo = escapedTwo == 1 ? 2 : 0;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
case 'F':
|
|
||||||
if (escapedTwo == 2)
|
|
||||||
_ambiguous.add(Ambiguous.SEPARATOR);
|
|
||||||
escapedTwo = 0;
|
|
||||||
break;
|
|
||||||
case '5':
|
|
||||||
if (escapedTwo == 2)
|
|
||||||
_ambiguous.add(Ambiguous.ENCODING);
|
|
||||||
escapedTwo = 0;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
escapedTwo = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PARAM:
|
case PARAM:
|
||||||
|
@ -1223,7 +1285,7 @@ public interface HttpURI
|
||||||
state = State.FRAGMENT;
|
state = State.FRAGMENT;
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
encoded = true;
|
encodedPath = true;
|
||||||
segment = i + 1;
|
segment = i + 1;
|
||||||
state = State.PATH;
|
state = State.PATH;
|
||||||
break;
|
break;
|
||||||
|
@ -1238,11 +1300,26 @@ public interface HttpURI
|
||||||
}
|
}
|
||||||
case QUERY:
|
case QUERY:
|
||||||
{
|
{
|
||||||
if (c == '#')
|
switch (c)
|
||||||
{
|
{
|
||||||
|
case '%':
|
||||||
|
encodedCharacters = 2;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
case 'U':
|
||||||
|
if (encodedCharacters == 1)
|
||||||
|
_violations.add(Violation.UTF16);
|
||||||
|
encodedCharacters = 0;
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
_query = uri.substring(mark, i);
|
_query = uri.substring(mark, i);
|
||||||
mark = i + 1;
|
mark = i + 1;
|
||||||
state = State.FRAGMENT;
|
state = State.FRAGMENT;
|
||||||
|
encodedCharacters = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
encodedCharacters = 0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1300,7 +1377,7 @@ public interface HttpURI
|
||||||
throw new IllegalStateException(state.toString());
|
throw new IllegalStateException(state.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!encoded && !dot)
|
if (!encodedPath && !dot)
|
||||||
{
|
{
|
||||||
if (_param == null)
|
if (_param == null)
|
||||||
_decodedPath = _path;
|
_decodedPath = _path;
|
||||||
|
@ -1333,7 +1410,7 @@ public interface HttpURI
|
||||||
// Empty segments are only ambiguous if they are not the last segment
|
// Empty segments are only ambiguous if they are not the last segment
|
||||||
// So if this method is called for any segment and we have previously seen an empty segment, then it was ambiguous
|
// So if this method is called for any segment and we have previously seen an empty segment, then it was ambiguous
|
||||||
if (_emptySegment)
|
if (_emptySegment)
|
||||||
_ambiguous.add(Ambiguous.EMPTY);
|
_violations.add(Violation.EMPTY);
|
||||||
|
|
||||||
if (end == segment)
|
if (end == segment)
|
||||||
{
|
{
|
||||||
|
@ -1344,7 +1421,7 @@ public interface HttpURI
|
||||||
// If this empty segment is the first segment then it is ambiguous.
|
// If this empty segment is the first segment then it is ambiguous.
|
||||||
if (segment == 0)
|
if (segment == 0)
|
||||||
{
|
{
|
||||||
_ambiguous.add(Ambiguous.EMPTY);
|
_violations.add(Violation.EMPTY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1361,12 +1438,12 @@ public interface HttpURI
|
||||||
if (ambiguous == Boolean.TRUE)
|
if (ambiguous == Boolean.TRUE)
|
||||||
{
|
{
|
||||||
// The segment is always ambiguous.
|
// The segment is always ambiguous.
|
||||||
_ambiguous.add(Ambiguous.SEGMENT);
|
_violations.add(Violation.SEGMENT);
|
||||||
}
|
}
|
||||||
else if (param && ambiguous == Boolean.FALSE)
|
else if (param && ambiguous == Boolean.FALSE)
|
||||||
{
|
{
|
||||||
// The segment is ambiguous only when followed by a parameter.
|
// The segment is ambiguous only when followed by a parameter.
|
||||||
_ambiguous.add(Ambiguous.PARAM);
|
_violations.add(Violation.PARAM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,11 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
/**
|
/**
|
||||||
* Allow Non canonical ambiguous paths. eg <code>/foo/x%2f%2e%2e%/bar</code> provided to applications as <code>/foo/x/../bar</code>
|
* Allow Non canonical ambiguous paths. eg <code>/foo/x%2f%2e%2e%/bar</code> provided to applications as <code>/foo/x/../bar</code>
|
||||||
*/
|
*/
|
||||||
NON_CANONICAL_AMBIGUOUS_PATHS("https://tools.ietf.org/html/rfc3986#section-3.3", "Non canonical ambiguous paths");
|
NON_CANONICAL_AMBIGUOUS_PATHS("https://tools.ietf.org/html/rfc3986#section-3.3", "Non canonical ambiguous paths"),
|
||||||
|
/**
|
||||||
|
* Allow UTF-16 encoding eg <code>/foo%u2192bar</code>.
|
||||||
|
*/
|
||||||
|
UTF16_ENCODINGS("https://www.w3.org/International/iri-edit/draft-duerst-iri.html#anchor29", "UTF16 encoding");
|
||||||
|
|
||||||
private final String _url;
|
private final String _url;
|
||||||
private final String _description;
|
private final String _description;
|
||||||
|
@ -109,9 +113,15 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT},
|
* LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT},
|
||||||
* {@link Violation#AMBIGUOUS_EMPTY_SEGMENT}, {@link Violation#AMBIGUOUS_PATH_SEPARATOR} and {@link Violation#AMBIGUOUS_PATH_ENCODING}.
|
* {@link Violation#AMBIGUOUS_EMPTY_SEGMENT}, {@link Violation#AMBIGUOUS_PATH_SEPARATOR}, {@link Violation#AMBIGUOUS_PATH_ENCODING}
|
||||||
|
* and {@link Violation#UTF16_ENCODINGS}
|
||||||
*/
|
*/
|
||||||
public static final UriCompliance LEGACY = new UriCompliance("LEGACY", of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_ENCODING, Violation.AMBIGUOUS_EMPTY_SEGMENT));
|
public static final UriCompliance LEGACY = new UriCompliance("LEGACY",
|
||||||
|
of(Violation.AMBIGUOUS_PATH_SEGMENT,
|
||||||
|
Violation.AMBIGUOUS_PATH_SEPARATOR,
|
||||||
|
Violation.AMBIGUOUS_PATH_ENCODING,
|
||||||
|
Violation.AMBIGUOUS_EMPTY_SEGMENT,
|
||||||
|
Violation.UTF16_ENCODINGS));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compliance mode that exactly follows RFC3986, including allowing all additional ambiguous URI Violations,
|
* Compliance mode that exactly follows RFC3986, including allowing all additional ambiguous URI Violations,
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
|
||||||
//
|
|
||||||
// This program and the accompanying materials are made available under the
|
|
||||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
||||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
|
||||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
||||||
|
|
||||||
public class HttpURIParseTest
|
|
||||||
{
|
|
||||||
public static Stream<Arguments> data()
|
|
||||||
{
|
|
||||||
return Stream.of(
|
|
||||||
|
|
||||||
// Nothing but path
|
|
||||||
Arguments.of("path", null, null, "-1", "path", null, null, null),
|
|
||||||
Arguments.of("path/path", null, null, "-1", "path/path", null, null, null),
|
|
||||||
Arguments.of("%65ncoded/path", null, null, "-1", "%65ncoded/path", null, null, null),
|
|
||||||
|
|
||||||
// Basic path reference
|
|
||||||
Arguments.of("/path/to/context", null, null, "-1", "/path/to/context", null, null, null),
|
|
||||||
|
|
||||||
// Basic with encoded query
|
|
||||||
Arguments.of("http://example.com/path/to/context;param?query=%22value%22#fragment", "http", "example.com", "-1", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
|
||||||
Arguments.of("http://[::1]/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "-1", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
|
||||||
|
|
||||||
// Basic with parameters and query
|
|
||||||
Arguments.of("http://example.com:8080/path/to/context;param?query=%22value%22#fragment", "http", "example.com", "8080", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
|
||||||
Arguments.of("http://[::1]:8080/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "8080", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
|
||||||
|
|
||||||
// Path References
|
|
||||||
Arguments.of("/path/info", null, null, null, "/path/info", null, null, null),
|
|
||||||
Arguments.of("/path/info#fragment", null, null, null, "/path/info", null, null, "fragment"),
|
|
||||||
Arguments.of("/path/info?query", null, null, null, "/path/info", null, "query", null),
|
|
||||||
Arguments.of("/path/info?query#fragment", null, null, null, "/path/info", null, "query", "fragment"),
|
|
||||||
Arguments.of("/path/info;param", null, null, null, "/path/info;param", "param", null, null),
|
|
||||||
Arguments.of("/path/info;param#fragment", null, null, null, "/path/info;param", "param", null, "fragment"),
|
|
||||||
Arguments.of("/path/info;param?query", null, null, null, "/path/info;param", "param", "query", null),
|
|
||||||
Arguments.of("/path/info;param?query#fragment", null, null, null, "/path/info;param", "param", "query", "fragment"),
|
|
||||||
Arguments.of("/path/info;a=b/foo;c=d", null, null, null, "/path/info;a=b/foo;c=d", "c=d", null, null), // TODO #405
|
|
||||||
|
|
||||||
// Protocol Less (aka scheme-less) URIs
|
|
||||||
Arguments.of("//host/path/info", null, "host", null, "/path/info", null, null, null),
|
|
||||||
Arguments.of("//user@host/path/info", null, "host", null, "/path/info", null, null, null),
|
|
||||||
Arguments.of("//user@host:8080/path/info", null, "host", "8080", "/path/info", null, null, null),
|
|
||||||
Arguments.of("//host:8080/path/info", null, "host", "8080", "/path/info", null, null, null),
|
|
||||||
|
|
||||||
// Host Less
|
|
||||||
Arguments.of("http:/path/info", "http", null, null, "/path/info", null, null, null),
|
|
||||||
Arguments.of("http:/path/info#fragment", "http", null, null, "/path/info", null, null, "fragment"),
|
|
||||||
Arguments.of("http:/path/info?query", "http", null, null, "/path/info", null, "query", null),
|
|
||||||
Arguments.of("http:/path/info?query#fragment", "http", null, null, "/path/info", null, "query", "fragment"),
|
|
||||||
Arguments.of("http:/path/info;param", "http", null, null, "/path/info;param", "param", null, null),
|
|
||||||
Arguments.of("http:/path/info;param#fragment", "http", null, null, "/path/info;param", "param", null, "fragment"),
|
|
||||||
Arguments.of("http:/path/info;param?query", "http", null, null, "/path/info;param", "param", "query", null),
|
|
||||||
Arguments.of("http:/path/info;param?query#fragment", "http", null, null, "/path/info;param", "param", "query", "fragment"),
|
|
||||||
|
|
||||||
// Everything and the kitchen sink
|
|
||||||
Arguments.of("http://user@host:8080/path/info;param?query#fragment", "http", "host", "8080", "/path/info;param", "param", "query", "fragment"),
|
|
||||||
Arguments.of("xxxxx://user@host:8080/path/info;param?query#fragment", "xxxxx", "host", "8080", "/path/info;param", "param", "query", "fragment"),
|
|
||||||
|
|
||||||
// No host, parameter with no content
|
|
||||||
Arguments.of("http:///;?#", "http", null, null, "/;", "", "", ""),
|
|
||||||
|
|
||||||
// Path with query that has no value
|
|
||||||
Arguments.of("/path/info?a=?query", null, null, null, "/path/info", null, "a=?query", null),
|
|
||||||
|
|
||||||
// Path with query alt syntax
|
|
||||||
Arguments.of("/path/info?a=;query", null, null, null, "/path/info", null, "a=;query", null),
|
|
||||||
|
|
||||||
// URI with host character
|
|
||||||
Arguments.of("/@path/info", null, null, null, "/@path/info", null, null, null),
|
|
||||||
Arguments.of("/user@path/info", null, null, null, "/user@path/info", null, null, null),
|
|
||||||
Arguments.of("//user@host/info", null, "host", null, "/info", null, null, null),
|
|
||||||
Arguments.of("//@host/info", null, "host", null, "/info", null, null, null),
|
|
||||||
Arguments.of("@host/info", null, null, null, "@host/info", null, null, null),
|
|
||||||
|
|
||||||
// Scheme-less, with host and port (overlapping with path)
|
|
||||||
Arguments.of("//host:8080//", null, "host", "8080", "//", null, null, null),
|
|
||||||
|
|
||||||
// File reference
|
|
||||||
Arguments.of("file:///path/info", "file", null, null, "/path/info", null, null, null),
|
|
||||||
Arguments.of("file:/path/info", "file", null, null, "/path/info", null, null, null),
|
|
||||||
|
|
||||||
// Bad URI (no scheme, no host, no path)
|
|
||||||
Arguments.of("//", null, null, null, null, null, null, null),
|
|
||||||
|
|
||||||
// Simple localhost references
|
|
||||||
Arguments.of("http://localhost/", "http", "localhost", null, "/", null, null, null),
|
|
||||||
Arguments.of("http://localhost:8080/", "http", "localhost", "8080", "/", null, null, null),
|
|
||||||
Arguments.of("http://localhost/?x=y", "http", "localhost", null, "/", null, "x=y", null),
|
|
||||||
|
|
||||||
// Simple path with parameter
|
|
||||||
Arguments.of("/;param", null, null, null, "/;param", "param", null, null),
|
|
||||||
Arguments.of(";param", null, null, null, ";param", "param", null, null),
|
|
||||||
|
|
||||||
// Simple path with query
|
|
||||||
Arguments.of("/?x=y", null, null, null, "/", null, "x=y", null),
|
|
||||||
Arguments.of("/?abc=test", null, null, null, "/", null, "abc=test", null),
|
|
||||||
|
|
||||||
// Simple path with fragment
|
|
||||||
Arguments.of("/#fragment", null, null, null, "/", null, null, "fragment"),
|
|
||||||
|
|
||||||
// Simple IPv4 host with port (default path)
|
|
||||||
Arguments.of("http://192.0.0.1:8080/", "http", "192.0.0.1", "8080", "/", null, null, null),
|
|
||||||
|
|
||||||
// Simple IPv6 host with port (default path)
|
|
||||||
|
|
||||||
Arguments.of("http://[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null),
|
|
||||||
// IPv6 authenticated host with port (default path)
|
|
||||||
|
|
||||||
Arguments.of("http://user@[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null),
|
|
||||||
|
|
||||||
// Simple IPv6 host no port (default path)
|
|
||||||
Arguments.of("http://[2001:db8::1]/", "http", "[2001:db8::1]", null, "/", null, null, null),
|
|
||||||
|
|
||||||
// Scheme-less IPv6, host with port (default path)
|
|
||||||
Arguments.of("//[2001:db8::1]:8080/", null, "[2001:db8::1]", "8080", "/", null, null, null),
|
|
||||||
|
|
||||||
// Interpreted as relative path of "*" (no host/port/scheme/query/fragment)
|
|
||||||
Arguments.of("*", null, null, null, "*", null, null, null),
|
|
||||||
|
|
||||||
// Path detection Tests (seen from JSP/JSTL and <c:url> use)
|
|
||||||
Arguments.of("http://host:8080/path/info?q1=v1&q2=v2", "http", "host", "8080", "/path/info", null, "q1=v1&q2=v2", null),
|
|
||||||
Arguments.of("/path/info?q1=v1&q2=v2", null, null, null, "/path/info", null, "q1=v1&q2=v2", null),
|
|
||||||
Arguments.of("/info?q1=v1&q2=v2", null, null, null, "/info", null, "q1=v1&q2=v2", null),
|
|
||||||
Arguments.of("info?q1=v1&q2=v2", null, null, null, "info", null, "q1=v1&q2=v2", null),
|
|
||||||
Arguments.of("info;q1=v1?q2=v2", null, null, null, "info;q1=v1", "q1=v1", "q2=v2", null),
|
|
||||||
|
|
||||||
// Path-less, query only (seen from JSP/JSTL and <c:url> use)
|
|
||||||
Arguments.of("?q1=v1&q2=v2", null, null, null, "", null, "q1=v1&q2=v2", null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("data")
|
|
||||||
public void testParseString(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment) throws Exception
|
|
||||||
{
|
|
||||||
HttpURI httpUri = HttpURI.from(input);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
new URI(input);
|
|
||||||
// URI is valid (per java.net.URI parsing)
|
|
||||||
|
|
||||||
// Test case sanity check
|
|
||||||
assertThat("[" + input + "] expected path (test case) cannot be null", path, notNullValue());
|
|
||||||
|
|
||||||
// Assert expectations
|
|
||||||
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme));
|
|
||||||
assertThat("[" + input + "] .host", httpUri.getHost(), is(host));
|
|
||||||
assertThat("[" + input + "] .port", httpUri.getPort(), is(port == null ? -1 : port));
|
|
||||||
assertThat("[" + input + "] .path", httpUri.getPath(), is(path));
|
|
||||||
assertThat("[" + input + "] .param", httpUri.getParam(), is(param));
|
|
||||||
assertThat("[" + input + "] .query", httpUri.getQuery(), is(query));
|
|
||||||
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(fragment));
|
|
||||||
assertThat("[" + input + "] .toString", httpUri.toString(), is(input));
|
|
||||||
}
|
|
||||||
catch (URISyntaxException e)
|
|
||||||
{
|
|
||||||
// Assert HttpURI values for invalid URI (such as "//")
|
|
||||||
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(nullValue()));
|
|
||||||
assertThat("[" + input + "] .host", httpUri.getHost(), is(nullValue()));
|
|
||||||
assertThat("[" + input + "] .port", httpUri.getPort(), is(-1));
|
|
||||||
assertThat("[" + input + "] .path", httpUri.getPath(), is(nullValue()));
|
|
||||||
assertThat("[" + input + "] .param", httpUri.getParam(), is(nullValue()));
|
|
||||||
assertThat("[" + input + "] .query", httpUri.getQuery(), is(nullValue()));
|
|
||||||
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(nullValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("data")
|
|
||||||
public void testParseURI(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment) throws Exception
|
|
||||||
{
|
|
||||||
URI javaUri = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
javaUri = new URI(input);
|
|
||||||
}
|
|
||||||
catch (URISyntaxException ignore)
|
|
||||||
{
|
|
||||||
// Ignore, as URI is invalid anyway
|
|
||||||
}
|
|
||||||
assumeTrue(javaUri != null, "Skipping, not a valid input URI: " + input);
|
|
||||||
|
|
||||||
HttpURI httpUri = HttpURI.from(javaUri);
|
|
||||||
|
|
||||||
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme));
|
|
||||||
assertThat("[" + input + "] .host", httpUri.getHost(), is(host));
|
|
||||||
assertThat("[" + input + "] .port", httpUri.getPort(), is(port == null ? -1 : port));
|
|
||||||
assertThat("[" + input + "] .path", httpUri.getPath(), is(path));
|
|
||||||
assertThat("[" + input + "] .param", httpUri.getParam(), is(param));
|
|
||||||
assertThat("[" + input + "] .query", httpUri.getQuery(), is(query));
|
|
||||||
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(fragment));
|
|
||||||
|
|
||||||
assertThat("[" + input + "] .toString", httpUri.toString(), is(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("data")
|
|
||||||
public void testCompareToJavaNetURI(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment) throws Exception
|
|
||||||
{
|
|
||||||
URI javaUri = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
javaUri = new URI(input);
|
|
||||||
}
|
|
||||||
catch (URISyntaxException ignore)
|
|
||||||
{
|
|
||||||
// Ignore, as URI is invalid anyway
|
|
||||||
}
|
|
||||||
assumeTrue(javaUri != null, "Skipping, not a valid input URI");
|
|
||||||
|
|
||||||
HttpURI httpUri = HttpURI.from(javaUri);
|
|
||||||
|
|
||||||
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(javaUri.getScheme()));
|
|
||||||
assertThat("[" + input + "] .host", httpUri.getHost(), is(javaUri.getHost()));
|
|
||||||
assertThat("[" + input + "] .port", httpUri.getPort(), is(javaUri.getPort()));
|
|
||||||
assertThat("[" + input + "] .path", httpUri.getPath(), is(javaUri.getRawPath()));
|
|
||||||
// Not Relevant for java.net.URI -- assertThat("["+input+"] .param", httpUri.getParam(), is(param));
|
|
||||||
assertThat("[" + input + "] .query", httpUri.getQuery(), is(javaUri.getRawQuery()));
|
|
||||||
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(javaUri.getFragment()));
|
|
||||||
assertThat("[" + input + "] .toString", httpUri.toString(), is(javaUri.toASCIIString()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,11 +13,13 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpURI.Ambiguous;
|
import org.eclipse.jetty.http.HttpURI.Violation;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
@ -25,12 +27,14 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
|
|
||||||
public class HttpURITest
|
public class HttpURITest
|
||||||
{
|
{
|
||||||
|
@ -323,109 +327,140 @@ public class HttpURITest
|
||||||
return Arrays.stream(new Object[][]
|
return Arrays.stream(new Object[][]
|
||||||
{
|
{
|
||||||
// Simple path example
|
// Simple path example
|
||||||
{"http://host/path/info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"http://host/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"//host/path/info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"//host/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
|
// Scheme & host containing unusual valid characters
|
||||||
|
{"ht..tp://host/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
{"ht1.2+..-3.4tp://127.0.0.1:8080/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
{"http://h%2est/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
{"http://h..est/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// legal non ambiguous relative paths
|
// legal non ambiguous relative paths
|
||||||
{"http://host/../path/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"http://host/../path/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
{"http://host/path/../info", "/info", EnumSet.noneOf(Ambiguous.class)},
|
{"http://host/path/../info", "/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"http://host/path/./info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"http://host/path/./info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"//host/path/../info", "/info", EnumSet.noneOf(Ambiguous.class)},
|
{"//host/path/../info", "/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"//host/path/./info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"//host/path/./info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/../info", "/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/../info", "/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/./info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/./info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"path/../info", "info", EnumSet.noneOf(Ambiguous.class)},
|
{"path/../info", "info", EnumSet.noneOf(Violation.class)},
|
||||||
{"path/./info", "path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"path/./info", "path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
|
// encoded paths
|
||||||
|
{"/f%6f%6F/bar", "/foo/bar", EnumSet.noneOf(Violation.class)},
|
||||||
|
{"/f%u006f%u006F/bar", "/foo/bar", EnumSet.of(Violation.UTF16)},
|
||||||
|
|
||||||
// illegal paths
|
// illegal paths
|
||||||
{"//host/../path/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"//host/../path/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
{"/../path/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"/../path/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
{"../path/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"../path/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/%XX/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"/path/%XX/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/%2/F/info", null, EnumSet.noneOf(Ambiguous.class)},
|
{"/path/%2/F/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
|
{"/path/%/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
|
{"/path/%u000X/info", null, EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// ambiguous dot encodings
|
// ambiguous dot encodings
|
||||||
{"scheme://host/path/%2e/info", "/path/./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"scheme://host/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"scheme:/path/%2e/info", "/path/./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"scheme:/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e/info", "/path/./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"path/%2e/info/", "path/./info/", EnumSet.of(Ambiguous.SEGMENT)},
|
{"path/%2e/info/", "path/./info/", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e/info", "./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e/info", "./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e%2e/info", "../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%u002e/info", "./info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
{"%2e%2e;/info", "../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e%2e/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e", ".", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%u002e%u002e/info", "../info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
{"%2e.", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e%2e;/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{".%2e", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%u002e%u002e;/info", "../info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
{"%2e%2e", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e", ".", EnumSet.of(Violation.SEGMENT)},
|
||||||
|
{"%u002e", ".", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
{"%2e.", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
|
{"%u002e.", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
{".%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
|
{".%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
{"%2e%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
|
{"%u002e%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
{"%2e%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
{"%u002e%2e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||||
|
|
||||||
// empty segment treated as ambiguous
|
// empty segment treated as ambiguous
|
||||||
{"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo///../../../bar", "/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo/./../bar", "/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"foo/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{"foo;/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{";/bar", "/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{";/bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{";?n=v", "", EnumSet.of(Ambiguous.EMPTY)},
|
{";?n=v", "", EnumSet.of(Violation.EMPTY)},
|
||||||
{"?n=v", "", EnumSet.noneOf(Ambiguous.class)},
|
{"?n=v", "", EnumSet.noneOf(Violation.class)},
|
||||||
{"#n=v", "", EnumSet.noneOf(Ambiguous.class)},
|
{"#n=v", "", EnumSet.noneOf(Violation.class)},
|
||||||
{"", "", EnumSet.noneOf(Ambiguous.class)},
|
{"", "", EnumSet.noneOf(Violation.class)},
|
||||||
{"http:/foo", "/foo", EnumSet.noneOf(Ambiguous.class)},
|
{"http:/foo", "/foo", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// ambiguous parameter inclusions
|
// ambiguous parameter inclusions
|
||||||
{"/path/.;/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/.;/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/.;param/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/.;param/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/..;/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/..;/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/..;param/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/..;param/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||||
{".;/info", "./info", EnumSet.of(Ambiguous.PARAM)},
|
{".;/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||||
{".;param/info", "./info", EnumSet.of(Ambiguous.PARAM)},
|
{".;param/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"..;/info", "../info", EnumSet.of(Ambiguous.PARAM)},
|
{"..;/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||||
{"..;param/info", "../info", EnumSet.of(Ambiguous.PARAM)},
|
{"..;param/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||||
|
|
||||||
// ambiguous segment separators
|
// ambiguous segment separators
|
||||||
{"/path/%2f/info", "/path///info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"%2f/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"%2f/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"%2F/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"%2F/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"/path/%2f../info", "/path//../info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"/path/%2f../info", "/path//../info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
|
|
||||||
// ambiguous encoding
|
// ambiguous encoding
|
||||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Ambiguous.ENCODING)},
|
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.ENCODING)},
|
||||||
{"%25/info", "%/info", EnumSet.of(Ambiguous.ENCODING)},
|
{"/path/%u0025/info", "/path/%/info", EnumSet.of(Violation.ENCODING, Violation.UTF16)},
|
||||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Ambiguous.ENCODING)},
|
{"%25/info", "%/info", EnumSet.of(Violation.ENCODING)},
|
||||||
|
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.ENCODING)},
|
||||||
|
{"/path/%u0025../info", "/path/%../info", EnumSet.of(Violation.ENCODING, Violation.UTF16)},
|
||||||
|
|
||||||
// combinations
|
// combinations
|
||||||
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM)},
|
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM)},
|
||||||
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT)},
|
{"/path/%u002f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.UTF16)},
|
||||||
|
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT)},
|
||||||
|
|
||||||
// Non ascii characters
|
// Non ascii characters
|
||||||
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
||||||
{"http://localhost:9000/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Ambiguous.class)},
|
{"http://localhost:9000/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)},
|
||||||
{"http://localhost:9000/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Ambiguous.class)},
|
{"http://localhost:9000/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)},
|
||||||
// @checkstyle-enable-check : AvoidEscapedUnicodeCharactersCheck
|
// @checkstyle-enable-check : AvoidEscapedUnicodeCharactersCheck
|
||||||
}).map(Arguments::of);
|
}).map(Arguments::of);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("decodePathTests")
|
@MethodSource("decodePathTests")
|
||||||
public void testDecodedPath(String input, String decodedPath, EnumSet<Ambiguous> expected)
|
public void testDecodedPath(String input, String decodedPath, EnumSet<Violation> expected)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpURI uri = HttpURI.from(input);
|
HttpURI uri = HttpURI.from(input);
|
||||||
assertThat(uri.getDecodedPath(), is(decodedPath));
|
assertThat(uri.getDecodedPath(), is(decodedPath));
|
||||||
assertThat(uri.isAmbiguous(), is(!expected.isEmpty()));
|
EnumSet<Violation> ambiguous = EnumSet.copyOf(expected);
|
||||||
assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Ambiguous.SEGMENT)));
|
ambiguous.retainAll(EnumSet.complementOf(EnumSet.of(Violation.UTF16)));
|
||||||
assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Ambiguous.SEPARATOR)));
|
|
||||||
assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Ambiguous.PARAM)));
|
assertThat(uri.isAmbiguous(), is(!ambiguous.isEmpty()));
|
||||||
assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Ambiguous.ENCODING)));
|
assertThat(uri.hasAmbiguousSegment(), is(ambiguous.contains(Violation.SEGMENT)));
|
||||||
|
assertThat(uri.hasAmbiguousSeparator(), is(ambiguous.contains(Violation.SEPARATOR)));
|
||||||
|
assertThat(uri.hasAmbiguousParameter(), is(ambiguous.contains(Violation.PARAM)));
|
||||||
|
assertThat(uri.hasAmbiguousEncoding(), is(ambiguous.contains(Violation.ENCODING)));
|
||||||
|
|
||||||
|
assertThat(uri.hasUtf16Encoding(), is(expected.contains(Violation.UTF16)));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
if (decodedPath != null)
|
||||||
|
e.printStackTrace();
|
||||||
assertThat(decodedPath, nullValue());
|
assertThat(decodedPath, nullValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,13 +470,13 @@ public class HttpURITest
|
||||||
return Arrays.stream(new Object[][]
|
return Arrays.stream(new Object[][]
|
||||||
{
|
{
|
||||||
// Simple path example
|
// Simple path example
|
||||||
{"/path/info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// legal non ambiguous relative paths
|
// legal non ambiguous relative paths
|
||||||
{"/path/../info", "/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/../info", "/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/./info", "/path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/./info", "/path/info", EnumSet.noneOf(Violation.class)},
|
||||||
{"path/../info", "info", EnumSet.noneOf(Ambiguous.class)},
|
{"path/../info", "info", EnumSet.noneOf(Violation.class)},
|
||||||
{"path/./info", "path/info", EnumSet.noneOf(Ambiguous.class)},
|
{"path/./info", "path/info", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// illegal paths
|
// illegal paths
|
||||||
{"/../path/info", null, null},
|
{"/../path/info", null, null},
|
||||||
|
@ -450,82 +485,82 @@ public class HttpURITest
|
||||||
{"/path/%2/F/info", null, null},
|
{"/path/%2/F/info", null, null},
|
||||||
|
|
||||||
// ambiguous dot encodings
|
// ambiguous dot encodings
|
||||||
{"/path/%2e/info", "/path/./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"path/%2e/info/", "path/./info/", EnumSet.of(Ambiguous.SEGMENT)},
|
{"path/%2e/info/", "path/./info/", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e/info", "./info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e/info", "./info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e%2e/info", "../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e%2e/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e%2e;/info", "../info", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e%2e;/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e", ".", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e", ".", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e.", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e.", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
{".%2e", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{".%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
{"%2e%2e", "..", EnumSet.of(Ambiguous.SEGMENT)},
|
{"%2e%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||||
|
|
||||||
// empty segment treated as ambiguous
|
// empty segment treated as ambiguous
|
||||||
{"/", "/", EnumSet.noneOf(Ambiguous.class)},
|
{"/", "/", EnumSet.noneOf(Violation.class)},
|
||||||
{"/#", "/", EnumSet.noneOf(Ambiguous.class)},
|
{"/#", "/", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path", "/path", EnumSet.noneOf(Ambiguous.class)},
|
{"/path", "/path", EnumSet.noneOf(Violation.class)},
|
||||||
{"/path/", "/path/", EnumSet.noneOf(Ambiguous.class)},
|
{"/path/", "/path/", EnumSet.noneOf(Violation.class)},
|
||||||
{"//", "//", EnumSet.of(Ambiguous.EMPTY)},
|
{"//", "//", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//", "/foo//", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//", "/foo//", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"//foo/bar", "//foo/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"//foo/bar", "//foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo?bar", "/foo", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo?bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo#bar", "/foo", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo#bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo;bar", "/foo", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo;bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo/?bar", "/foo/", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo/?bar", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo/#bar", "/foo/", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo/#bar", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo/;param", "/foo/", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo/;param", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo/;param/bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo/;param/bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//bar//", "/foo//bar//", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//bar//", "/foo//bar//", EnumSet.of(Violation.EMPTY)},
|
||||||
{"//foo//bar//", "//foo//bar//", EnumSet.of(Ambiguous.EMPTY)},
|
{"//foo//bar//", "//foo//bar//", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo///../../../bar", "/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"/foo/./../bar", "/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Ambiguous.EMPTY)},
|
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{"foo/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{"foo;/bar", "foo/bar", EnumSet.noneOf(Ambiguous.class)},
|
{"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||||
{";/bar", "/bar", EnumSet.of(Ambiguous.EMPTY)},
|
{";/bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||||
{";?n=v", "", EnumSet.of(Ambiguous.EMPTY)},
|
{";?n=v", "", EnumSet.of(Violation.EMPTY)},
|
||||||
{"?n=v", "", EnumSet.noneOf(Ambiguous.class)},
|
{"?n=v", "", EnumSet.noneOf(Violation.class)},
|
||||||
{"#n=v", "", EnumSet.noneOf(Ambiguous.class)},
|
{"#n=v", "", EnumSet.noneOf(Violation.class)},
|
||||||
{"", "", EnumSet.noneOf(Ambiguous.class)},
|
{"", "", EnumSet.noneOf(Violation.class)},
|
||||||
|
|
||||||
// ambiguous parameter inclusions
|
// ambiguous parameter inclusions
|
||||||
{"/path/.;/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/.;/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/.;param/info", "/path/./info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/.;param/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/..;/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/..;/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||||
{"/path/..;param/info", "/path/../info", EnumSet.of(Ambiguous.PARAM)},
|
{"/path/..;param/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||||
{".;/info", "./info", EnumSet.of(Ambiguous.PARAM)},
|
{".;/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||||
{".;param/info", "./info", EnumSet.of(Ambiguous.PARAM)},
|
{".;param/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||||
{"..;/info", "../info", EnumSet.of(Ambiguous.PARAM)},
|
{"..;/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||||
{"..;param/info", "../info", EnumSet.of(Ambiguous.PARAM)},
|
{"..;param/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||||
|
|
||||||
// ambiguous segment separators
|
// ambiguous segment separators
|
||||||
{"/path/%2f/info", "/path///info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"%2f/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"%2f/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"%2F/info", "//info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"%2F/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
{"/path/%2f../info", "/path//../info", EnumSet.of(Ambiguous.SEPARATOR)},
|
{"/path/%2f../info", "/path//../info", EnumSet.of(Violation.SEPARATOR)},
|
||||||
|
|
||||||
// ambiguous encoding
|
// ambiguous encoding
|
||||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Ambiguous.ENCODING)},
|
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.ENCODING)},
|
||||||
{"%25/info", "%/info", EnumSet.of(Ambiguous.ENCODING)},
|
{"%25/info", "%/info", EnumSet.of(Violation.ENCODING)},
|
||||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Ambiguous.ENCODING)},
|
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.ENCODING)},
|
||||||
|
|
||||||
// combinations
|
// combinations
|
||||||
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM)},
|
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM)},
|
||||||
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT)},
|
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT)},
|
||||||
{"/path/%2f/%25/..;/%2e//info", "/path///%/.././/info", EnumSet.of(Ambiguous.SEPARATOR, Ambiguous.PARAM, Ambiguous.SEGMENT, Ambiguous.ENCODING, Ambiguous.EMPTY)},
|
{"/path/%2f/%25/..;/%2e//info", "/path///%/.././/info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT, Violation.ENCODING, Violation.EMPTY)},
|
||||||
}).map(Arguments::of);
|
}).map(Arguments::of);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("testPathQueryTests")
|
@MethodSource("testPathQueryTests")
|
||||||
public void testPathQuery(String input, String decodedPath, EnumSet<Ambiguous> expected)
|
public void testPathQuery(String input, String decodedPath, EnumSet<Violation> expected)
|
||||||
{
|
{
|
||||||
// If expected is null then it is a bad URI and should throw.
|
// If expected is null then it is a bad URI and should throw.
|
||||||
if (expected == null)
|
if (expected == null)
|
||||||
|
@ -537,10 +572,225 @@ public class HttpURITest
|
||||||
HttpURI uri = HttpURI.build().pathQuery(input);
|
HttpURI uri = HttpURI.build().pathQuery(input);
|
||||||
assertThat(uri.getDecodedPath(), is(decodedPath));
|
assertThat(uri.getDecodedPath(), is(decodedPath));
|
||||||
assertThat(uri.isAmbiguous(), is(!expected.isEmpty()));
|
assertThat(uri.isAmbiguous(), is(!expected.isEmpty()));
|
||||||
assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Ambiguous.EMPTY)));
|
assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Violation.EMPTY)));
|
||||||
assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Ambiguous.SEGMENT)));
|
assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Violation.SEGMENT)));
|
||||||
assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Ambiguous.SEPARATOR)));
|
assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Violation.SEPARATOR)));
|
||||||
assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Ambiguous.PARAM)));
|
assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Violation.PARAM)));
|
||||||
assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Ambiguous.ENCODING)));
|
assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Violation.ENCODING)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> parseData()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
// Nothing but path
|
||||||
|
Arguments.of("path", null, null, "-1", "path", null, null, null),
|
||||||
|
Arguments.of("path/path", null, null, "-1", "path/path", null, null, null),
|
||||||
|
Arguments.of("%65ncoded/path", null, null, "-1", "%65ncoded/path", null, null, null),
|
||||||
|
|
||||||
|
// Basic path reference
|
||||||
|
Arguments.of("/path/to/context", null, null, "-1", "/path/to/context", null, null, null),
|
||||||
|
|
||||||
|
// Basic with encoded query
|
||||||
|
Arguments.of("http://example.com/path/to/context;param?query=%22value%22#fragment", "http", "example.com", "-1", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
||||||
|
Arguments.of("http://[::1]/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "-1", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
||||||
|
|
||||||
|
// Basic with parameters and query
|
||||||
|
Arguments.of("http://example.com:8080/path/to/context;param?query=%22value%22#fragment", "http", "example.com", "8080", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
||||||
|
Arguments.of("http://[::1]:8080/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "8080", "/path/to/context;param", "param", "query=%22value%22", "fragment"),
|
||||||
|
|
||||||
|
// Path References
|
||||||
|
Arguments.of("/path/info", null, null, null, "/path/info", null, null, null),
|
||||||
|
Arguments.of("/path/info#fragment", null, null, null, "/path/info", null, null, "fragment"),
|
||||||
|
Arguments.of("/path/info?query", null, null, null, "/path/info", null, "query", null),
|
||||||
|
Arguments.of("/path/info?query#fragment", null, null, null, "/path/info", null, "query", "fragment"),
|
||||||
|
Arguments.of("/path/info;param", null, null, null, "/path/info;param", "param", null, null),
|
||||||
|
Arguments.of("/path/info;param#fragment", null, null, null, "/path/info;param", "param", null, "fragment"),
|
||||||
|
Arguments.of("/path/info;param?query", null, null, null, "/path/info;param", "param", "query", null),
|
||||||
|
Arguments.of("/path/info;param?query#fragment", null, null, null, "/path/info;param", "param", "query", "fragment"),
|
||||||
|
Arguments.of("/path/info;a=b/foo;c=d", null, null, null, "/path/info;a=b/foo;c=d", "c=d", null, null), // TODO #405
|
||||||
|
|
||||||
|
// Protocol Less (aka scheme-less) URIs
|
||||||
|
Arguments.of("//host/path/info", null, "host", null, "/path/info", null, null, null),
|
||||||
|
Arguments.of("//user@host/path/info", null, "host", null, "/path/info", null, null, null),
|
||||||
|
Arguments.of("//user@host:8080/path/info", null, "host", "8080", "/path/info", null, null, null),
|
||||||
|
Arguments.of("//host:8080/path/info", null, "host", "8080", "/path/info", null, null, null),
|
||||||
|
|
||||||
|
// Host Less
|
||||||
|
Arguments.of("http:/path/info", "http", null, null, "/path/info", null, null, null),
|
||||||
|
Arguments.of("http:/path/info#fragment", "http", null, null, "/path/info", null, null, "fragment"),
|
||||||
|
Arguments.of("http:/path/info?query", "http", null, null, "/path/info", null, "query", null),
|
||||||
|
Arguments.of("http:/path/info?query#fragment", "http", null, null, "/path/info", null, "query", "fragment"),
|
||||||
|
Arguments.of("http:/path/info;param", "http", null, null, "/path/info;param", "param", null, null),
|
||||||
|
Arguments.of("http:/path/info;param#fragment", "http", null, null, "/path/info;param", "param", null, "fragment"),
|
||||||
|
Arguments.of("http:/path/info;param?query", "http", null, null, "/path/info;param", "param", "query", null),
|
||||||
|
Arguments.of("http:/path/info;param?query#fragment", "http", null, null, "/path/info;param", "param", "query", "fragment"),
|
||||||
|
|
||||||
|
// Everything and the kitchen sink
|
||||||
|
Arguments.of("http://user@host:8080/path/info;param?query#fragment", "http", "host", "8080", "/path/info;param", "param", "query", "fragment"),
|
||||||
|
Arguments.of("xxxxx://user@host:8080/path/info;param?query#fragment", "xxxxx", "host", "8080", "/path/info;param", "param", "query", "fragment"),
|
||||||
|
|
||||||
|
// No host, parameter with no content
|
||||||
|
Arguments.of("http:///;?#", "http", null, null, "/;", "", "", ""),
|
||||||
|
|
||||||
|
// Path with query that has no value
|
||||||
|
Arguments.of("/path/info?a=?query", null, null, null, "/path/info", null, "a=?query", null),
|
||||||
|
|
||||||
|
// Path with query alt syntax
|
||||||
|
Arguments.of("/path/info?a=;query", null, null, null, "/path/info", null, "a=;query", null),
|
||||||
|
|
||||||
|
// URI with host character
|
||||||
|
Arguments.of("/@path/info", null, null, null, "/@path/info", null, null, null),
|
||||||
|
Arguments.of("/user@path/info", null, null, null, "/user@path/info", null, null, null),
|
||||||
|
Arguments.of("//user@host/info", null, "host", null, "/info", null, null, null),
|
||||||
|
Arguments.of("//@host/info", null, "host", null, "/info", null, null, null),
|
||||||
|
Arguments.of("@host/info", null, null, null, "@host/info", null, null, null),
|
||||||
|
|
||||||
|
// Scheme-less, with host and port (overlapping with path)
|
||||||
|
Arguments.of("//host:8080//", null, "host", "8080", "//", null, null, null),
|
||||||
|
|
||||||
|
// File reference
|
||||||
|
Arguments.of("file:///path/info", "file", null, null, "/path/info", null, null, null),
|
||||||
|
Arguments.of("file:/path/info", "file", null, null, "/path/info", null, null, null),
|
||||||
|
|
||||||
|
// Bad URI (no scheme, no host, no path)
|
||||||
|
Arguments.of("//", null, null, null, null, null, null, null),
|
||||||
|
|
||||||
|
// Simple localhost references
|
||||||
|
Arguments.of("http://localhost/", "http", "localhost", null, "/", null, null, null),
|
||||||
|
Arguments.of("http://localhost:8080/", "http", "localhost", "8080", "/", null, null, null),
|
||||||
|
Arguments.of("http://localhost/?x=y", "http", "localhost", null, "/", null, "x=y", null),
|
||||||
|
|
||||||
|
// Simple path with parameter
|
||||||
|
Arguments.of("/;param", null, null, null, "/;param", "param", null, null),
|
||||||
|
Arguments.of(";param", null, null, null, ";param", "param", null, null),
|
||||||
|
|
||||||
|
// Simple path with query
|
||||||
|
Arguments.of("/?x=y", null, null, null, "/", null, "x=y", null),
|
||||||
|
Arguments.of("/?abc=test", null, null, null, "/", null, "abc=test", null),
|
||||||
|
|
||||||
|
// Simple path with fragment
|
||||||
|
Arguments.of("/#fragment", null, null, null, "/", null, null, "fragment"),
|
||||||
|
|
||||||
|
// Simple IPv4 host with port (default path)
|
||||||
|
Arguments.of("http://192.0.0.1:8080/", "http", "192.0.0.1", "8080", "/", null, null, null),
|
||||||
|
|
||||||
|
// Simple IPv6 host with port (default path)
|
||||||
|
|
||||||
|
Arguments.of("http://[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null),
|
||||||
|
// IPv6 authenticated host with port (default path)
|
||||||
|
|
||||||
|
Arguments.of("http://user@[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null),
|
||||||
|
|
||||||
|
// Simple IPv6 host no port (default path)
|
||||||
|
Arguments.of("http://[2001:db8::1]/", "http", "[2001:db8::1]", null, "/", null, null, null),
|
||||||
|
|
||||||
|
// Scheme-less IPv6, host with port (default path)
|
||||||
|
Arguments.of("//[2001:db8::1]:8080/", null, "[2001:db8::1]", "8080", "/", null, null, null),
|
||||||
|
|
||||||
|
// Interpreted as relative path of "*" (no host/port/scheme/query/fragment)
|
||||||
|
Arguments.of("*", null, null, null, "*", null, null, null),
|
||||||
|
|
||||||
|
// Path detection Tests (seen from JSP/JSTL and <c:url> use)
|
||||||
|
Arguments.of("http://host:8080/path/info?q1=v1&q2=v2", "http", "host", "8080", "/path/info", null, "q1=v1&q2=v2", null),
|
||||||
|
Arguments.of("/path/info?q1=v1&q2=v2", null, null, null, "/path/info", null, "q1=v1&q2=v2", null),
|
||||||
|
Arguments.of("/info?q1=v1&q2=v2", null, null, null, "/info", null, "q1=v1&q2=v2", null),
|
||||||
|
Arguments.of("info?q1=v1&q2=v2", null, null, null, "info", null, "q1=v1&q2=v2", null),
|
||||||
|
Arguments.of("info;q1=v1?q2=v2", null, null, null, "info;q1=v1", "q1=v1", "q2=v2", null),
|
||||||
|
|
||||||
|
// Path-less, query only (seen from JSP/JSTL and <c:url> use)
|
||||||
|
Arguments.of("?q1=v1&q2=v2", null, null, null, "", null, "q1=v1&q2=v2", null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("parseData")
|
||||||
|
public void testParseString(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment)
|
||||||
|
{
|
||||||
|
HttpURI httpUri = HttpURI.from(input);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new URI(input);
|
||||||
|
// URI is valid (per java.net.URI parsing)
|
||||||
|
|
||||||
|
// Test case sanity check
|
||||||
|
assertThat("[" + input + "] expected path (test case) cannot be null", path, notNullValue());
|
||||||
|
|
||||||
|
// Assert expectations
|
||||||
|
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme));
|
||||||
|
assertThat("[" + input + "] .host", httpUri.getHost(), is(host));
|
||||||
|
assertThat("[" + input + "] .port", httpUri.getPort(), is(port == null ? -1 : port));
|
||||||
|
assertThat("[" + input + "] .path", httpUri.getPath(), is(path));
|
||||||
|
assertThat("[" + input + "] .param", httpUri.getParam(), is(param));
|
||||||
|
assertThat("[" + input + "] .query", httpUri.getQuery(), is(query));
|
||||||
|
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(fragment));
|
||||||
|
assertThat("[" + input + "] .toString", httpUri.toString(), is(input));
|
||||||
|
}
|
||||||
|
catch (URISyntaxException e)
|
||||||
|
{
|
||||||
|
// Assert HttpURI values for invalid URI (such as "//")
|
||||||
|
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(nullValue()));
|
||||||
|
assertThat("[" + input + "] .host", httpUri.getHost(), is(nullValue()));
|
||||||
|
assertThat("[" + input + "] .port", httpUri.getPort(), is(-1));
|
||||||
|
assertThat("[" + input + "] .path", httpUri.getPath(), is(nullValue()));
|
||||||
|
assertThat("[" + input + "] .param", httpUri.getParam(), is(nullValue()));
|
||||||
|
assertThat("[" + input + "] .query", httpUri.getQuery(), is(nullValue()));
|
||||||
|
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(nullValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("parseData")
|
||||||
|
public void testParseURI(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment) throws Exception
|
||||||
|
{
|
||||||
|
URI javaUri = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
javaUri = new URI(input);
|
||||||
|
}
|
||||||
|
catch (URISyntaxException ignore)
|
||||||
|
{
|
||||||
|
// Ignore, as URI is invalid anyway
|
||||||
|
}
|
||||||
|
assumeTrue(javaUri != null, "Skipping, not a valid input URI: " + input);
|
||||||
|
|
||||||
|
HttpURI httpUri = HttpURI.from(javaUri);
|
||||||
|
|
||||||
|
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme));
|
||||||
|
assertThat("[" + input + "] .host", httpUri.getHost(), is(host));
|
||||||
|
assertThat("[" + input + "] .port", httpUri.getPort(), is(port == null ? -1 : port));
|
||||||
|
assertThat("[" + input + "] .path", httpUri.getPath(), is(path));
|
||||||
|
assertThat("[" + input + "] .param", httpUri.getParam(), is(param));
|
||||||
|
assertThat("[" + input + "] .query", httpUri.getQuery(), is(query));
|
||||||
|
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(fragment));
|
||||||
|
|
||||||
|
assertThat("[" + input + "] .toString", httpUri.toString(), is(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("parseData")
|
||||||
|
public void testCompareToJavaNetURI(String input, String scheme, String host, Integer port, String path, String param, String query, String fragment) throws Exception
|
||||||
|
{
|
||||||
|
URI javaUri = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
javaUri = new URI(input);
|
||||||
|
}
|
||||||
|
catch (URISyntaxException ignore)
|
||||||
|
{
|
||||||
|
// Ignore, as URI is invalid anyway
|
||||||
|
}
|
||||||
|
assumeTrue(javaUri != null, "Skipping, not a valid input URI");
|
||||||
|
|
||||||
|
HttpURI httpUri = HttpURI.from(javaUri);
|
||||||
|
|
||||||
|
assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(javaUri.getScheme()));
|
||||||
|
assertThat("[" + input + "] .host", httpUri.getHost(), is(javaUri.getHost()));
|
||||||
|
assertThat("[" + input + "] .port", httpUri.getPort(), is(javaUri.getPort()));
|
||||||
|
assertThat("[" + input + "] .path", httpUri.getPath(), is(javaUri.getRawPath()));
|
||||||
|
// Not Relevant for java.net.URI -- assertThat("["+input+"] .param", httpUri.getParam(), is(param));
|
||||||
|
assertThat("[" + input + "] .query", httpUri.getQuery(), is(javaUri.getRawQuery()));
|
||||||
|
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(javaUri.getFragment()));
|
||||||
|
assertThat("[" + input + "] .toString", httpUri.toString(), is(javaUri.toASCIIString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1687,23 +1687,32 @@ public class Request implements HttpServletRequest
|
||||||
_method = request.getMethod();
|
_method = request.getMethod();
|
||||||
_httpFields = request.getFields();
|
_httpFields = request.getFields();
|
||||||
final HttpURI uri = request.getURI();
|
final HttpURI uri = request.getURI();
|
||||||
|
boolean ambiguous = false;
|
||||||
UriCompliance compliance = null;
|
UriCompliance compliance = null;
|
||||||
boolean ambiguous = uri.isAmbiguous();
|
if (uri.hasViolations())
|
||||||
|
{
|
||||||
|
ambiguous = uri.isAmbiguous();
|
||||||
|
compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
|
||||||
|
if (compliance != null)
|
||||||
|
{
|
||||||
|
if (!compliance.allows(UriCompliance.Violation.UTF16_ENCODINGS) && uri.hasUtf16Encoding())
|
||||||
|
throw new BadMessageException("UTF16 % encoding not supported");
|
||||||
|
|
||||||
if (ambiguous)
|
if (ambiguous)
|
||||||
{
|
{
|
||||||
compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
|
if (uri.hasAmbiguousSegment() && !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT))
|
||||||
if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT)))
|
|
||||||
throw new BadMessageException("Ambiguous segment in URI");
|
throw new BadMessageException("Ambiguous segment in URI");
|
||||||
if (uri.hasAmbiguousEmptySegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT)))
|
if (uri.hasAmbiguousEmptySegment() && !compliance.allows(UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT))
|
||||||
throw new BadMessageException("Ambiguous empty segment in URI");
|
throw new BadMessageException("Ambiguous empty segment in URI");
|
||||||
if (uri.hasAmbiguousSeparator() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR)))
|
if (uri.hasAmbiguousSeparator() && !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR))
|
||||||
throw new BadMessageException("Ambiguous segment in URI");
|
throw new BadMessageException("Ambiguous segment in URI");
|
||||||
if (uri.hasAmbiguousParameter() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER)))
|
if (uri.hasAmbiguousParameter() && !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER))
|
||||||
throw new BadMessageException("Ambiguous path parameter in URI");
|
throw new BadMessageException("Ambiguous path parameter in URI");
|
||||||
if (uri.hasAmbiguousEncoding() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING)))
|
if (uri.hasAmbiguousEncoding() && !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING))
|
||||||
throw new BadMessageException("Ambiguous path encoding in URI");
|
throw new BadMessageException("Ambiguous path encoding in URI");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
|
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -37,7 +36,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.servlet.DispatcherType;
|
|
||||||
import jakarta.servlet.MultipartConfigElement;
|
import jakarta.servlet.MultipartConfigElement;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.ServletInputStream;
|
import jakarta.servlet.ServletInputStream;
|
||||||
|
@ -69,8 +67,6 @@ import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
import org.eclipse.jetty.server.session.Session;
|
import org.eclipse.jetty.server.session.Session;
|
||||||
import org.eclipse.jetty.server.session.SessionData;
|
import org.eclipse.jetty.server.session.SessionData;
|
||||||
import org.eclipse.jetty.server.session.SessionHandler;
|
import org.eclipse.jetty.server.session.SessionHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.FS;
|
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
@ -78,7 +74,6 @@ import org.eclipse.jetty.util.IO;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -1638,6 +1633,19 @@ public class RequestTest
|
||||||
assertEquals(0, request.getParameterMap().size());
|
assertEquals(0, request.getParameterMap().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncoding() throws Exception
|
||||||
|
{
|
||||||
|
_handler._checker = (request, response) -> "/foo/bar".equals(request.getPathInfo());
|
||||||
|
String request = "GET /f%6f%6F/b%u0061r HTTP/1.0\r\n" +
|
||||||
|
"Host: whatever\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
|
||||||
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
|
||||||
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
|
||||||
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAmbiguousParameters() throws Exception
|
public void testAmbiguousParameters() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -470,8 +470,7 @@ public class URIUtil
|
||||||
char u = path.charAt(i + 1);
|
char u = path.charAt(i + 1);
|
||||||
if (u == 'u')
|
if (u == 'u')
|
||||||
{
|
{
|
||||||
// TODO remove %u support in jetty-10
|
// UTF16 encoding is only supported with UriCompliance.Violation.UTF16_ENCODINGS.
|
||||||
// this is wrong. This is a codepoint not a char
|
|
||||||
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
|
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
|
||||||
i += 5;
|
i += 5;
|
||||||
}
|
}
|
||||||
|
@ -558,8 +557,7 @@ public class URIUtil
|
||||||
char u = path.charAt(i + 1);
|
char u = path.charAt(i + 1);
|
||||||
if (u == 'u')
|
if (u == 'u')
|
||||||
{
|
{
|
||||||
// TODO remove %u encoding support in jetty-10
|
// UTF16 encoding is only supported with UriCompliance.Violation.UTF16_ENCODINGS.
|
||||||
// This is wrong. This is a codepoint not a char
|
|
||||||
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
|
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
|
||||||
i += 5;
|
i += 5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,16 +309,22 @@ public class WebAppContextTest
|
||||||
|
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%u002e/%u002e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%u002e%u002e/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.OK_200));
|
||||||
|
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/ HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/ HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /web-inf/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /web-inf/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%u002e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2e/%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%u002e/%u002e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%2e%2e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /foo/%u002e%u002e/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2E/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%2E/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /%u002E/WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET //WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET //WEB-INF/test.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF%2ftest.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
assertThat(HttpTester.parseResponse(connector.getResponse("GET /WEB-INF%2ftest.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.tests.distribution;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class LoggingOptionsTests extends AbstractJettyHomeTest
|
||||||
|
{
|
||||||
|
public static Stream<Arguments> validLoggingModules()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("logging-jetty",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/jetty-slf4j-impl-.*\\.jar"),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-jetty"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Arguments.of("logging-logback",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/logback-classic-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/logback-core-.*\\.jar"
|
||||||
|
),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-logback"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Arguments.of("logging-jul",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/slf4j-jdk14-.*\\.jar"
|
||||||
|
),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-jul"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Arguments.of("logging-log4j1",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/slf4j-log4j12-.*\\.jar"
|
||||||
|
),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-log4j1"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Arguments.of("logging-log4j2",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/log4j-slf4j18-impl-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/log4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/log4j-core-.*\\.jar"
|
||||||
|
),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-log4j2"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// Disabled, as slf4j noop is not supported by output/log monitoring of AbstractJettyHomeTest
|
||||||
|
/* Arguments.of("logging-noop",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar"
|
||||||
|
), Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-log4j2"
|
||||||
|
)
|
||||||
|
),*/
|
||||||
|
Arguments.of("logging-logback,logging-jcl-capture,logging-jul-capture,logging-log4j1-capture",
|
||||||
|
Arrays.asList(
|
||||||
|
"\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/logback-classic-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/logback-core-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/jcl-over-slf4j-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/jul-to-slf4j-.*\\.jar",
|
||||||
|
"\\$\\{jetty.base\\}/lib/logging/log4j-over-slf4j-.*\\.jar"
|
||||||
|
),
|
||||||
|
Arrays.asList(
|
||||||
|
"logging/slf4j",
|
||||||
|
"logging-logback",
|
||||||
|
"logging-jcl-capture",
|
||||||
|
"logging-jul-capture",
|
||||||
|
"logging-log4j1-capture"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "[{index}] {0}")
|
||||||
|
@MethodSource("validLoggingModules")
|
||||||
|
public void testLoggingConfiguration(String loggingModules, List<String> expectedClasspathEntries, List<String> expectedEnabledModules) throws Exception
|
||||||
|
{
|
||||||
|
Path jettyBase = newTestJettyBaseDirectory();
|
||||||
|
String jettyVersion = System.getProperty("jettyVersion");
|
||||||
|
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
|
||||||
|
.jettyVersion(jettyVersion)
|
||||||
|
.jettyBase(jettyBase)
|
||||||
|
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String[] args1 = {
|
||||||
|
"--approve-all-licenses",
|
||||||
|
"--add-modules=resources,server,http,webapp,deploy,jsp,servlets",
|
||||||
|
"--add-modules=" + loggingModules
|
||||||
|
};
|
||||||
|
try (JettyHomeTester.Run installRun = distribution.start(args1))
|
||||||
|
{
|
||||||
|
assertTrue(installRun.awaitFor(10, TimeUnit.SECONDS));
|
||||||
|
assertEquals(0, installRun.getExitValue());
|
||||||
|
|
||||||
|
try (JettyHomeTester.Run listConfigRun = distribution.start("--list-config"))
|
||||||
|
{
|
||||||
|
assertTrue(listConfigRun.awaitFor(10, TimeUnit.SECONDS));
|
||||||
|
assertEquals(0, listConfigRun.getExitValue());
|
||||||
|
|
||||||
|
List<String> rawConfigLogs = new ArrayList<>();
|
||||||
|
rawConfigLogs.addAll(listConfigRun.getLogs());
|
||||||
|
|
||||||
|
for (String expectedEnabledModule : expectedEnabledModules)
|
||||||
|
{
|
||||||
|
containsEntryWith("Expected Enabled Module", rawConfigLogs, expectedEnabledModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String expectedClasspathEntry : expectedClasspathEntries)
|
||||||
|
{
|
||||||
|
containsEntryWith("Expected Classpath Entry", rawConfigLogs, expectedClasspathEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-jsp-webapp:war:" + jettyVersion);
|
||||||
|
distribution.installWarFile(war, "test");
|
||||||
|
|
||||||
|
int port = distribution.freePort();
|
||||||
|
try (JettyHomeTester.Run requestRun = distribution.start("jetty.http.port=" + port))
|
||||||
|
{
|
||||||
|
assertTrue(requestRun.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
startHttpClient();
|
||||||
|
ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertThat(response.getContentAsString(), containsString("JSP Examples"));
|
||||||
|
assertThat(response.getContentAsString(), not(containsString("<%")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void containsEntryWith(String reason, List<String> logs, String expectedEntry)
|
||||||
|
{
|
||||||
|
Pattern pat = Pattern.compile(expectedEntry);
|
||||||
|
assertThat("Count of matches for [" + expectedEntry + "]", logs.stream().filter(pat.asPredicate()).count(), greaterThanOrEqualTo(1L));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue