Skip to content

Commit da47f2e

Browse files
authored
Merge pull request #12874 from jetty/fix/10.0.x/userinfo-backport
Introduce UriCompliance.USER_INFO to Jetty 10.0.x
2 parents 2b2c2ae + 514e581 commit da47f2e

File tree

3 files changed

+150
-12
lines changed

3 files changed

+150
-12
lines changed

jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ static Immutable from(String scheme, String host, int port, String pathQuery)
145145
/**
146146
* Get a URI path parameter. Multiple and in segment parameters are ignored and only
147147
* the last trailing parameter is returned.
148+
*
148149
* @return The last path parameter or null
149150
*/
150151
String getParam();
@@ -526,6 +527,79 @@ private enum State
526527
.with("%u002e%u002e", Boolean.TRUE)
527528
.build();
528529

530+
private static final boolean[] __unreservedPctEncodedSubDelims;
531+
532+
private static boolean isHexDigit(char c)
533+
{
534+
return (((c >= 'a') && (c <= 'f')) || // ALPHA (lower)
535+
((c >= 'A') && (c <= 'F')) || // ALPHA (upper)
536+
((c >= '0') && (c <= '9')));
537+
}
538+
539+
private static boolean isUnreserved(char c)
540+
{
541+
return (((c >= 'a') && (c <= 'z')) || // ALPHA (lower)
542+
((c >= 'A') && (c <= 'Z')) || // ALPHA (upper)
543+
((c >= '0') && (c <= '9')) || // DIGIT
544+
(c == '-') || (c == '.') || (c == '_') || (c == '~'));
545+
}
546+
547+
private static boolean isSubDelim(char c)
548+
{
549+
return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || c == '=';
550+
}
551+
552+
static boolean isUnreservedPctEncodedOrSubDelim(char c)
553+
{
554+
return c < __unreservedPctEncodedSubDelims.length && __unreservedPctEncodedSubDelims[c];
555+
}
556+
557+
static
558+
{
559+
// Establish allowed and disallowed characters per the path rules of
560+
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
561+
// ABNF
562+
// path = path-abempty ; begins with "/" or is empty
563+
// / path-absolute ; begins with "/" but not "//"
564+
// / path-noscheme ; begins with a non-colon segment
565+
// / path-rootless ; begins with a segment
566+
// / path-empty ; zero characters
567+
// path-abempty = *( "/" segment )
568+
// path-absolute = "/" [ segment-nz *( "/" segment ) ]
569+
// path-noscheme = segment-nz-nc *( "/" segment )
570+
// path-rootless = segment-nz *( "/" segment )
571+
// path-empty = 0<pchar>
572+
//
573+
// segment = *pchar
574+
// segment-nz = 1*pchar
575+
// segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
576+
// ; non-zero-length segment without any colon ":"
577+
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
578+
// pct-encoded = "%" HEXDIG HEXDIG
579+
//
580+
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
581+
// reserved = gen-delims / sub-delims
582+
// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
583+
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
584+
// / "*" / "+" / "," / ";" / "="
585+
//
586+
// authority = [ userinfo "@" ] host [ ":" port ]
587+
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
588+
// host = IP-literal / IPv4address / reg-name
589+
// port = *DIGIT
590+
//
591+
// reg-name = *( unreserved / pct-encoded / sub-delims )
592+
//
593+
// we are limited to US-ASCII per https://datatracker.ietf.org/doc/html/rfc3986#section-2
594+
__unreservedPctEncodedSubDelims = new boolean[128];
595+
596+
for (int i = 0; i < __unreservedPctEncodedSubDelims.length; i++)
597+
{
598+
char c = (char)i;
599+
__unreservedPctEncodedSubDelims[i] = isUnreserved(c) || c == '%' || isSubDelim(c);
600+
}
601+
}
602+
529603
private String _scheme;
530604
private String _user;
531605
private String _host;
@@ -980,7 +1054,7 @@ private void parse(State state, final String uri)
9801054
int mark = 0; // the start of the current section being parsed
9811055
int pathMark = 0; // the start of the path section
9821056
int segment = 0; // the start of the current segment within the path
983-
boolean encodedPath = false; // set to true if the path contains % encoded characters
1057+
boolean encoded = false; // set to true if the path contains % encoded characters
9841058
boolean encodedUtf16 = false; // Is the current encoding for UTF16?
9851059
int encodedCharacters = 0; // partial state of parsing a % encoded character<x>
9861060
int encodedValue = 0; // the partial encoded value
@@ -1025,7 +1099,7 @@ private void parse(State state, final String uri)
10251099
state = State.ASTERISK;
10261100
break;
10271101
case '%':
1028-
encodedPath = true;
1102+
encoded = true;
10291103
encodedCharacters = 2;
10301104
encodedValue = 0;
10311105
mark = pathMark = segment = i;
@@ -1078,7 +1152,7 @@ private void parse(State state, final String uri)
10781152
break;
10791153
case '%':
10801154
// must have been in an encoded path
1081-
encodedPath = true;
1155+
encoded = true;
10821156
encodedCharacters = 2;
10831157
encodedValue = 0;
10841158
state = State.PATH;
@@ -1128,7 +1202,10 @@ private void parse(State state, final String uri)
11281202
switch (c)
11291203
{
11301204
case '/':
1205+
if (encodedCharacters > 0)
1206+
throw new IllegalArgumentException("Bad authority");
11311207
_host = uri.substring(mark, i);
1208+
encoded = false;
11321209
pathMark = mark = i;
11331210
segment = mark + 1;
11341211
state = State.PATH;
@@ -1143,12 +1220,35 @@ private void parse(State state, final String uri)
11431220
if (_user != null)
11441221
throw new IllegalArgumentException("Bad authority");
11451222
_user = uri.substring(mark, i);
1223+
_violations.add(Violation.USER_INFO);
11461224
mark = i + 1;
11471225
break;
11481226
case '[':
1227+
if (i != mark)
1228+
throw new IllegalArgumentException("Bad authority");
11491229
state = State.IPV6;
11501230
break;
1231+
case '%':
1232+
if (encodedCharacters > 0)
1233+
throw new IllegalArgumentException("Bad authority");
1234+
encodedCharacters = 2;
1235+
encoded = true;
1236+
break;
1237+
case '#':
1238+
case ';':
1239+
throw new IllegalArgumentException("Bad authority");
1240+
11511241
default:
1242+
if (encodedCharacters > 0)
1243+
{
1244+
if (!isHexDigit(c))
1245+
throw new IllegalArgumentException("Bad authority");
1246+
encodedCharacters--;
1247+
}
1248+
else if (!isUnreservedPctEncodedOrSubDelim(c))
1249+
{
1250+
throw new IllegalArgumentException("Bad authority");
1251+
}
11521252
break;
11531253
}
11541254
break;
@@ -1173,7 +1273,11 @@ private void parse(State state, final String uri)
11731273
state = State.PATH;
11741274
}
11751275
break;
1276+
case ':':
1277+
break;
11761278
default:
1279+
if (!isHexDigit(c))
1280+
throw new IllegalArgumentException("Bad authority");
11771281
break;
11781282
}
11791283
break;
@@ -1186,6 +1290,7 @@ private void parse(State state, final String uri)
11861290
throw new IllegalArgumentException("Bad authority");
11871291
// It wasn't a port, but a password!
11881292
_user = _host + ":" + uri.substring(mark, i);
1293+
_violations.add(Violation.USER_INFO);
11891294
mark = i + 1;
11901295
state = State.HOST;
11911296
}
@@ -1261,7 +1366,7 @@ else if (c == '/')
12611366
dot |= segment == i;
12621367
break;
12631368
case '%':
1264-
encodedPath = true;
1369+
encoded = true;
12651370
encodedUtf16 = false;
12661371
encodedCharacters = 2;
12671372
encodedValue = 0;
@@ -1289,7 +1394,7 @@ else if (c == '/')
12891394
state = State.FRAGMENT;
12901395
break;
12911396
case '/':
1292-
encodedPath = true;
1397+
encoded = true;
12931398
segment = i + 1;
12941399
state = State.PATH;
12951400
break;
@@ -1368,7 +1473,7 @@ else if (c == '/')
13681473
throw new IllegalStateException(state.toString());
13691474
}
13701475

1371-
if (!encodedPath && !dot)
1476+
if (!encoded && !dot)
13721477
{
13731478
if (_param == null)
13741479
_decodedPath = _path;

jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ public enum Violation implements ComplianceViolation
6969
/**
7070
* Allow UTF-16 encoding eg <code>/foo%u2192bar</code>.
7171
*/
72-
UTF16_ENCODINGS("https://www.w3.org/International/iri-edit/draft-duerst-iri.html#anchor29", "UTF16 encoding");
72+
UTF16_ENCODINGS("https://www.w3.org/International/iri-edit/draft-duerst-iri.html#anchor29", "UTF16 encoding"),
73+
/**
74+
* Allow user info in the authority portion of the URI and HTTP specs.
75+
*/
76+
USER_INFO("https://datatracker.ietf.org/doc/html/rfc9110#name-deprecation-of-userinfo-in-", "Deprecated User Info");
7377

7478
private final String _url;
7579
private final String _description;

jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,8 @@ public void testParse()
148148
assertThat(uri.getHost(), is("foo"));
149149
assertThat(uri.getPath(), is("/bar"));
150150

151-
// We do allow nulls if not encoded. This can be used for testing 2nd line of defence.
152-
builder.uri("http://fo\000/bar");
153-
uri = builder.asImmutable();
154-
assertThat(uri.getHost(), is("fo\000"));
155-
assertThat(uri.getPath(), is("/bar"));
151+
// We do not allow nulls if not encoded.
152+
assertThrows(IllegalArgumentException.class, () -> builder.uri("http://fo\000/bar").asImmutable());
156153
}
157154

158155
@Test
@@ -892,4 +889,36 @@ public void testRelativePathWithAuthority()
892889
.path("");
893890
assertEquals("//host", uri.asString());
894891
}
892+
893+
public static Stream<String> badAuthorities()
894+
{
895+
return Stream.of(
896+
"http://#host/path",
897+
"https:// host/path",
898+
"https://h st/path",
899+
"https://h\000st/path",
900+
"https://h%GGst/path",
901+
"https://host%/path",
902+
"https://host%0/path",
903+
"https://host%u001f/path",
904+
"https://host%:8080/path",
905+
"https://host%0:8080/path",
906+
"https://user%@host/path",
907+
"https://user%0@host/path",
908+
"https://host:notport/path",
909+
"https://user@host:notport/path",
910+
"https://user:password@host:notport/path",
911+
"https://user @host.com/",
912+
"https://user#@host.com/",
913+
"https://[notIpv6]/",
914+
"https://bad[0::1::2::3::4]/"
915+
);
916+
}
917+
918+
@ParameterizedTest
919+
@MethodSource("badAuthorities")
920+
public void testBadAuthority(String uri)
921+
{
922+
assertThrows(IllegalArgumentException.class, () -> HttpURI.from(uri));
923+
}
895924
}

0 commit comments

Comments
 (0)