Skip to content

Commit e376b1a

Browse files
authored
HttpURI.Mutable path changes should clear out any existing path violations before parsing the new path. (#12306)
* HttpURI.Mutable path changes should clear out any existing path violations before parsing the new path. Fixes: #11298
1 parent 97ff548 commit e376b1a

3 files changed

Lines changed: 99 additions & 12 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,9 @@ public Mutable path(String path)
994994
throw new IllegalArgumentException("Relative path with authority");
995995
if (!URIUtil.isPathValid(path))
996996
throw new IllegalArgumentException("Path not correctly encoded: " + path);
997+
// since we are resetting the path, lets clear out the path specific violations.
998+
if (_violations != null)
999+
_violations.removeIf(UriCompliance::isPathViolation);
9971000
_uri = null;
9981001
_path = null;
9991002
_canonicalPath = null;
@@ -1016,6 +1019,9 @@ public Mutable pathQuery(String pathQuery)
10161019
{
10171020
if (hasAuthority() && !isPathValidForAuthority(pathQuery))
10181021
throw new IllegalArgumentException("Relative path with authority");
1022+
// since we are resetting the path, lets clear out the path specific violations.
1023+
if (_violations != null)
1024+
_violations.removeIf(UriCompliance::isPathViolation);
10191025
_uri = null;
10201026
_path = null;
10211027
_canonicalPath = null;

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,29 @@ public String getDescription()
145145
}
146146

147147
public static final Set<Violation> NO_VIOLATION = Collections.unmodifiableSet(EnumSet.noneOf(Violation.class));
148+
149+
/**
150+
* Set of violations that can trigger a HttpURI.isAmbiguous violation.
151+
*/
148152
public static final Set<Violation> AMBIGUOUS_VIOLATIONS = Collections.unmodifiableSet(EnumSet.of(
149153
Violation.AMBIGUOUS_EMPTY_SEGMENT,
150154
Violation.AMBIGUOUS_PATH_ENCODING,
151155
Violation.AMBIGUOUS_PATH_PARAMETER,
152156
Violation.AMBIGUOUS_PATH_SEGMENT,
153157
Violation.AMBIGUOUS_PATH_SEPARATOR));
154158

159+
/**
160+
* List of Violations that apply only to the HttpURI.path section.
161+
*/
162+
private static final Set<Violation> PATH_VIOLATIONS = Collections.unmodifiableSet(EnumSet.of(
163+
Violation.AMBIGUOUS_EMPTY_SEGMENT,
164+
Violation.AMBIGUOUS_PATH_ENCODING,
165+
Violation.AMBIGUOUS_PATH_PARAMETER,
166+
Violation.AMBIGUOUS_PATH_SEGMENT,
167+
Violation.AMBIGUOUS_PATH_SEPARATOR,
168+
Violation.SUSPICIOUS_PATH_CHARACTERS,
169+
Violation.ILLEGAL_PATH_CHARACTERS));
170+
155171
/**
156172
* Compliance mode that exactly follows <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>,
157173
* excluding all URI Violations.
@@ -352,6 +368,17 @@ public UriCompliance without(String name, Violation... violations)
352368
return new UriCompliance(name, remainder);
353369
}
354370

371+
/**
372+
* Test if violation is referencing a HttpURI.path violation.
373+
*
374+
* @param violation the violation to test.
375+
* @return true if violation is a path violation.
376+
*/
377+
public static boolean isPathViolation(UriCompliance.Violation violation)
378+
{
379+
return PATH_VIOLATIONS.contains(violation);
380+
}
381+
355382
@Override
356383
public String toString()
357384
{

jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CompactPathRuleTest.java

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313

1414
package org.eclipse.jetty.rewrite.handler;
1515

16+
import java.io.ByteArrayInputStream;
17+
import java.io.ByteArrayOutputStream;
18+
import java.io.IOException;
19+
import java.nio.ByteBuffer;
20+
import java.util.Objects;
21+
import java.util.Properties;
1622
import java.util.stream.Stream;
1723

1824
import org.eclipse.jetty.http.HttpStatus;
1925
import org.eclipse.jetty.http.HttpTester;
2026
import org.eclipse.jetty.http.HttpURI;
2127
import org.eclipse.jetty.http.UriCompliance;
22-
import org.eclipse.jetty.io.Content;
2328
import org.eclipse.jetty.server.Handler;
2429
import org.eclipse.jetty.server.Request;
2530
import org.eclipse.jetty.server.Response;
@@ -36,14 +41,20 @@ public static Stream<Arguments> scenarios()
3641
{
3742
return Stream.of(
3843
// shouldn't change anything
39-
Arguments.of("/foo", null, "/foo", null, "/foo"),
40-
Arguments.of("/", null, "/", null, "/"),
44+
Arguments.of("/foo", null, "/foo", "", "/foo"),
45+
Arguments.of("/", null, "/", "", "/"),
4146
// simple compact path
42-
Arguments.of("////foo", null, "/foo", null, "/foo"),
47+
Arguments.of("////foo", null, "/foo", "", "/foo"),
4348
// with simple query
4449
Arguments.of("//foo//bar", "a=b", "/foo/bar", "a=b", "/foo/bar?a=b"),
4550
// with query that has double slashes (should preserve slashes in query)
46-
Arguments.of("//foo//bar", "a=b//c", "/foo/bar", "a=b//c", "/foo/bar?a=b//c")
51+
Arguments.of("//foo//bar", "a=b//c", "/foo/bar", "a=b//c", "/foo/bar?a=b//c"),
52+
// with ambiguous path parameter
53+
Arguments.of("//foo/..;/bar", "a=b//c", "/bar", "a=b//c", "/bar?a=b//c"),
54+
// with ambiguous path separator (not changed)
55+
Arguments.of("//foo/b%2far", "a=b//c", "/foo/b%2Far", "a=b//c", "/foo/b%2Far?a=b//c"),
56+
// with ambiguous path encoding (not changed)
57+
Arguments.of("//foo/%2562ar", "a=b//c", "/foo/%2562ar", "a=b//c", "/foo/%2562ar?a=b//c")
4758
);
4859
}
4960

@@ -59,24 +70,67 @@ public void testCompactPathRule(String inputPath, String inputQuery, String expe
5970
@Override
6071
public boolean handle(Request request, Response response, Callback callback)
6172
{
62-
Content.Sink.write(response, true, request.getHttpURI().getPathQuery(), callback);
73+
Properties props = new Properties();
74+
HttpURI httpURI = request.getHttpURI();
75+
props.setProperty("uri.path", of(httpURI.getPath()));
76+
props.setProperty("uri.query", of(httpURI.getQuery()));
77+
props.setProperty("uri.pathQuery", of(httpURI.getPathQuery()));
78+
props.setProperty("uri.hasViolations", of(httpURI.hasViolations()));
79+
props.setProperty("uri.isAmbiguous", of(httpURI.isAmbiguous()));
80+
props.setProperty("uri.hasAmbiguousEmptySegment", of(httpURI.hasAmbiguousEmptySegment()));
81+
props.setProperty("uri.hasAmbiguousEncoding", of(httpURI.hasAmbiguousEncoding()));
82+
props.setProperty("uri.hasAmbiguousParameter", of(httpURI.hasAmbiguousParameter()));
83+
props.setProperty("uri.hasAmbiguousSeparator", of(httpURI.hasAmbiguousSeparator()));
84+
props.setProperty("uri.hasAmbiguousSegment", of(httpURI.hasAmbiguousSegment()));
85+
try (ByteArrayOutputStream out = new ByteArrayOutputStream())
86+
{
87+
props.store(out, "HttpURI State");
88+
response.write(true, ByteBuffer.wrap(out.toByteArray()), callback);
89+
}
90+
catch (IOException e)
91+
{
92+
callback.failed(e);
93+
}
6394
return true;
6495
}
96+
97+
private String of(Object obj)
98+
{
99+
if (obj == null)
100+
return "";
101+
if (obj instanceof Boolean)
102+
return Boolean.toString((Boolean)obj);
103+
return Objects.toString(obj);
104+
}
65105
});
66106

67107

68108
String request = """
69109
GET %s HTTP/1.1
70110
Host: localhost
71-
111+
72112
""".formatted(HttpURI.build().path(inputPath).query(inputQuery));
73113

74114
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request));
75-
System.err.println(response.getReason());
76115
assertEquals(HttpStatus.OK_200, response.getStatus());
77-
HttpURI.Mutable result = HttpURI.build(response.getContent());
78-
assertEquals(expectedPath, result.getPath());
79-
assertEquals(expectedQuery, result.getQuery());
80-
assertEquals(expectedPathQuery, result.getPathQuery());
116+
Properties props = new Properties();
117+
try (ByteArrayInputStream in = new ByteArrayInputStream(response.getContentBytes()))
118+
{
119+
props.load(in);
120+
assertEquals(expectedPath, props.getProperty("uri.path"));
121+
assertEquals(expectedQuery, props.getProperty("uri.query"));
122+
assertEquals(expectedPathQuery, props.getProperty("uri.pathQuery"));
123+
124+
boolean ambiguousPathSep = inputPath.contains("%2f");
125+
boolean ambiguousPathEncoding = inputPath.contains("%25");
126+
127+
assertEquals(Boolean.toString(ambiguousPathSep || ambiguousPathEncoding), props.getProperty("uri.isAmbiguous"));
128+
assertEquals(Boolean.toString(ambiguousPathSep || ambiguousPathEncoding), props.getProperty("uri.hasViolations"));
129+
assertEquals("false", props.getProperty("uri.hasAmbiguousEmptySegment"));
130+
assertEquals(Boolean.toString(ambiguousPathEncoding), props.getProperty("uri.hasAmbiguousEncoding"));
131+
assertEquals("false", props.getProperty("uri.hasAmbiguousParameter"));
132+
assertEquals(Boolean.toString(ambiguousPathSep), props.getProperty("uri.hasAmbiguousSeparator"));
133+
assertEquals("false", props.getProperty("uri.hasAmbiguousSegment"));
134+
}
81135
}
82136
}

0 commit comments

Comments
 (0)