Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ public HeadersCallback(HttpExchange exchange, HttpContent content, Callback call
String query = request.getQuery();
if (query != null)
path += "?" + query;
metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
HttpURI uri = new HttpURI();
uri.parseRequestTarget(request.getMethod(), path);
metaData = new MetaData.Request(request.getMethod(), uri, request.getVersion(), request.getHeaders(), contentLength);
metaData.setTrailerSupplier(request.getTrailers());

if (!expects100Continue(request))
Expand Down
32 changes: 26 additions & 6 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ private void parse(State state, final String uri, final int offset, final int en
switch (c)
{
case '/':
mark = i;
pathMark = mark = i;
segment = mark + 1;
state = State.HOST_OR_PATH;
break;
case ';':
Expand Down Expand Up @@ -471,6 +472,8 @@ private void parse(State state, final String uri, final int offset, final int en
pathMark = segment = i;
state = State.PATH;
break;
case ':':
throw new IllegalArgumentException("Bad Scheme");
default:
mark = i;
if (_scheme == null)
Expand All @@ -491,7 +494,7 @@ private void parse(State state, final String uri, final int offset, final int en
{
case ':':
// must have been a scheme
_scheme = uri.substring(mark, i);
_scheme = URIUtil.validateScheme(uri.substring(mark, i));
// Start again with scheme set
state = State.START;
break;
Expand Down Expand Up @@ -596,8 +599,19 @@ private void parse(State state, final String uri, final int offset, final int en
encoded = true;
break;
case '#':
case '?':
case ';':
throw new IllegalArgumentException("Bad authority");
if (encodedCharacters > 0)
throw new IllegalArgumentException("Bad authority");
_host = uri.substring(mark, i);
if (_host.isEmpty())
throw new IllegalArgumentException("Bad authority");
encoded = false;
pathMark = mark = i;
segment = mark + 1;
state = State.PATH;
i--;
break;

default:
if (encodedCharacters > 0)
Expand All @@ -621,8 +635,13 @@ else if (!isUnreservedPctEncodedOrSubDelim(c))
case '/':
throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
case ']':
c = uri.charAt(++i);
_host = uri.substring(mark, i);
i++;
String host = uri.substring(mark, i);
URIUtil.validateInetAddress(host);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this only occur during a detected IPLiteral only? not a hostname right?
If it's a hostname, the resulting call to InetAddress.getByName will incur a DNS hit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. IPV6 state is only entered after seeing the corresponding [ in HOST state. The state machine here is not as refined as in 12, so there are probably still a few more strange cases here.... but I don't want to substantively change the behavior.

_host = host;
if (i == end)
break;
c = uri.charAt(i);
if (c == ':')
{
mark = i + 1;
Expand All @@ -637,7 +656,7 @@ else if (!isUnreservedPctEncodedOrSubDelim(c))
case ':':
break;
default:
if (!isHexDigit(c))
if (!isHexDigit(c) && c != '.')
throw new IllegalArgumentException("Bad authority");
break;
}
Expand Down Expand Up @@ -805,6 +824,7 @@ else if (c == '/')
break;
case SCHEME_OR_PATH:
case HOST_OR_PATH:
checkSegment(uri, segment, end, false);
_path = uri.substring(mark, end);
break;
case HOST:
Expand Down
84 changes: 80 additions & 4 deletions jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
Expand Down Expand Up @@ -158,6 +160,19 @@ public void testAt() throws Exception
assertEquals("/@foo/bar", uri.getPath());
}

/**
* Test of an HttpURI of just a "/".
*/
@Test
public void testFromSlash()
{
HttpURI uri = new HttpURI("/");
assertFalse(uri.hasViolations());
assertNull(uri.getScheme());
assertNull(uri.getAuthority());
assertEquals("/", uri.getPath());
}

@Test
public void testParams() throws Exception
{
Expand Down Expand Up @@ -684,6 +699,8 @@ public static Stream<Arguments> parseData()

// Simple IPv6 host no port (default path)
Arguments.of("http://[2001:db8::1]/", "http", "[2001:db8::1]", null, "/", null, null, null),
Arguments.of("http://[0:0:0:0:0:ffff:127.0.0.1]/", "http", "[0:0:0:0:0:ffff:127.0.0.1]", null, "/", null, null, null),
Arguments.of("http://[::ffff:127.0.0.1]/", "http", "[::ffff:127.0.0.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),
Expand Down Expand Up @@ -725,7 +742,8 @@ public void testParseString(String input, String scheme, String host, Integer po
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));
if (!input.contains(":ffff:127.0.0.1"))
assertThat("[" + input + "] .toString", httpUri.toString(), is(input));
}
catch (URISyntaxException e)
{
Expand Down Expand Up @@ -758,7 +776,8 @@ public void testParseURI(String input, String scheme, String host, Integer port,
HttpURI httpUri = new HttpURI(input);

assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme));
assertThat("[" + input + "] .host", httpUri.getHost(), is(host));
if (!input.contains(":ffff:127.0.0.1"))
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));
Expand Down Expand Up @@ -871,6 +890,24 @@ public void testRelativePathWithAuthority()
assertEquals("//host", uri.toString());
}

public static Stream<String> badSchemes()
{
return Stream.of(
"://host/path",
"\t://host/path",
" ://host/path",
"unknown^://host/path",
"http^://host/path"
);
}

@ParameterizedTest
@MethodSource("badSchemes")
public void testBadSchemes(String uri)
{
assertThrows(IllegalArgumentException.class, () -> new HttpURI(uri));
}

public static Stream<String> badAuthorities()
{
return Stream.of(
Expand All @@ -890,9 +927,11 @@ public static Stream<String> badAuthorities()
"https://user@host:notport/path",
"https://user:password@host:notport/path",
"https://user @host.com/",
"https://user#@host.com/",
"https://[notIpv6]/",
"https://bad[0::1::2::3::4]/"
"https://bad[0::1::2::3::4]/",
"http://[normal.com@]vulndetector.com/",
"http://normal.com[user@vulndetector].com/",
"http://normal.com[@]vulndetector.com/"
);
}

Expand All @@ -902,4 +941,41 @@ public void testBadAuthority(String uri)
{
assertThrows(IllegalArgumentException.class, () -> new HttpURI(uri));
}

public static Stream<Arguments> authoritiesNoPath()
{
return Stream.of(
Arguments.of("http://good.com#@evil.com", "good.com", null, "@evil.com"),
Arguments.of("http://[email protected]", "good.com", "@evil.com", null)
);
}

@ParameterizedTest
@MethodSource("authoritiesNoPath")
public void testAuthorityNoPath(String uri, String authority, String query, String fragment)
{
HttpURI httpURI = new HttpURI(uri);
assertThat(httpURI.getAuthority(), is(authority));
assertThat(httpURI.getPath(), is(""));
assertThat(httpURI.getQuery(), is(query));
assertThat(httpURI.getFragment(), is(fragment));
}

public static Stream<Arguments> connectURIs()
{
return Stream.of(
Arguments.of("localhost:8080"),
Arguments.of("127.0.0.1:8080"),
Arguments.of("[::1]:8080")
);
}

@ParameterizedTest
@MethodSource("connectURIs")
public void testConnect(String authority)
{
HttpURI httpURI = new HttpURI();
httpURI.parseRequestTarget(HttpMethod.CONNECT.asString(), authority);
assertThat(httpURI.getAuthority(), is(authority));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ public void testUseragentWithout(String logType) throws Exception
setup(logType);
testHandlerServerStart();

_connector.getResponse("GET http://[:1]/foo HTTP/1.1\nReferer: http://other.site\n\n");
_connector.getResponse("GET http://[::1]/foo HTTP/1.1\nReferer: http://other.site\n\n");
String log = _entries.poll(5, TimeUnit.SECONDS);
assertThat(log, containsString("GET http://[:1]/foo "));
assertThat(log, containsString("GET http://[::1]/foo "));
assertThat(log, containsString(" 400 50 \"http://other.site\" \"-\""));
}

Expand All @@ -371,9 +371,9 @@ public void testUseragentWith(String logType) throws Exception
setup(logType);
testHandlerServerStart();

_connector.getResponse("GET http://[:1]/foo HTTP/1.1\nReferer: http://other.site\nUser-Agent: Mozilla/5.0 (test)\n\n");
_connector.getResponse("GET http://[::1]/foo HTTP/1.1\nReferer: http://other.site\nUser-Agent: Mozilla/5.0 (test)\n\n");
String log = _entries.poll(5, TimeUnit.SECONDS);
assertThat(log, containsString("GET http://[:1]/foo "));
assertThat(log, containsString("GET http://[::1]/foo "));
assertThat(log, containsString(" 400 50 \"http://other.site\" \"Mozilla/5.0 (test)\""));
}

Expand Down
69 changes: 69 additions & 0 deletions jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.eclipse.jetty.util;

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -1345,6 +1346,74 @@ public static URI addPath(URI uri, String path)
return URI.create(buf.toString());
}

private static boolean isHexDigit(char c)
{
return (((c >= 'a') && (c <= 'f')) || // ALPHA (lower)
((c >= 'A') && (c <= 'F')) || // ALPHA (upper)
((c >= '0') && (c <= '9')));
}

/**
* Validate an IPv4 or IPv6 address.
* @param inetAddress the address to validate
* @throws IllegalArgumentException if the address is not valid
*/
public static void validateInetAddress(String inetAddress)
{
try
{
InetAddress ignored = InetAddress.getByName(inetAddress);
}
catch (Throwable e)
{
throw new IllegalArgumentException("Bad [IPv6] address", e);
}
}

/**
* Validate and normalize the scheme,
*
* @param scheme The scheme to normalize
* @return The normalized version of the scheme
* @throws IllegalArgumentException If the scheme is not valid
*/
public static String validateScheme(String scheme)
{
if (scheme == null || scheme.isEmpty())
throw new IllegalArgumentException("Bad scheme");

// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
StringBuilder toLowerCase = null;
for (int i = 0; i < scheme.length(); i++)
{
char c = scheme.charAt(i);
if (c >= 'A' && c <= 'Z')
{
if (toLowerCase == null)
{
toLowerCase = new StringBuilder(scheme.length());
toLowerCase.append(scheme, 0, i);
}
toLowerCase.append(Character.toLowerCase(c));
}
else if (c >= 'a' && c <= 'z' ||
(i > 0 && (c >= '0' && c <= '9' ||
c == '.' ||
c == '+' ||
c == '-')))
{
if (toLowerCase != null)
toLowerCase.append(c);
}
else
{
throw new IllegalArgumentException("Bad scheme");
}
}

return toLowerCase == null ? scheme : toLowerCase.toString();
}

/**
* Combine two query strings into one. Each query string should not contain the beginning '?' character, but
* may contain multiple parameters separated by the '{@literal &}' character.
Expand Down