Skip to content

Commit 5ce70c3

Browse files
committed
Minor Fix
1 parent 3767bcc commit 5ce70c3

4 files changed

Lines changed: 108 additions & 19 deletions

File tree

httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,15 +1118,89 @@ public URIBuilder normalizeSyntax() {
11181118
* @since 5.3
11191119
*/
11201120
public URIBuilder optimize() {
1121+
final String currentScheme = this.scheme;
1122+
if (currentScheme != null) {
1123+
this.scheme = TextUtils.toLowerCase(currentScheme);
1124+
}
1125+
// Preserve historical behavior: do not canonicalize rootless / opaque references.
1126+
if (this.pathRootless) {
1127+
return this;
1128+
}
11211129
final String raw = this.toString();
11221130
try {
11231131
final Rfc3986Uri u = Rfc3986Uri.parse(raw).optimize();
1124-
return new URIBuilder(u.toString());
1125-
} catch (final IllegalArgumentException | URISyntaxException ex) {
1132+
digestRfc3986Uri(u);
1133+
return this;
1134+
} catch (final IllegalArgumentException ex) {
11261135
return this;
11271136
}
11281137
}
11291138

1139+
private void digestRfc3986Uri(final Rfc3986Uri uri) {
1140+
this.scheme = uri.getScheme();
1141+
this.encodedUserInfo = uri.getUserInfo();
1142+
this.userInfo = PercentCodec.decode(uri.getUserInfo(), this.charset);
1143+
1144+
final String rawHost = uri.getHost();
1145+
this.host = toInternalHost(rawHost);
1146+
this.port = uri.getPort();
1147+
this.encodedAuthority = rawHost != null ? buildRawAuthority(uri.getUserInfo(), rawHost, uri.getPort()) : null;
1148+
1149+
this.encodedPath = uri.getPath();
1150+
this.pathSegments = parsePath(uri.getPath(), this.charset);
1151+
this.pathRootless = uri.getPath() == null || !uri.getPath().startsWith("/");
1152+
1153+
this.encodedQuery = uri.getQuery();
1154+
this.queryParams = parseQuery(uri.getQuery(), this.charset, this.plusAsBlank);
1155+
this.query = null;
1156+
1157+
this.encodedFragment = uri.getFragment();
1158+
this.fragment = PercentCodec.decode(uri.getFragment(), this.charset);
1159+
1160+
this.encodedSchemeSpecificPart = this.scheme != null
1161+
? buildRawSchemeSpecificPart(this.encodedAuthority, this.encodedPath, this.encodedQuery)
1162+
: null;
1163+
}
1164+
1165+
private static String toInternalHost(final String rawHost) {
1166+
if (rawHost == null) {
1167+
return null;
1168+
}
1169+
if (!rawHost.isEmpty() && rawHost.charAt(0) == '[' && rawHost.charAt(rawHost.length() - 1) == ']') {
1170+
return ZoneIdSupport.decodeZoneId(rawHost.substring(1, rawHost.length() - 1));
1171+
}
1172+
return rawHost;
1173+
}
1174+
1175+
private static String buildRawAuthority(final String rawUserInfo, final String rawHost, final int port) {
1176+
final StringBuilder sb = new StringBuilder();
1177+
if (rawUserInfo != null) {
1178+
sb.append(rawUserInfo).append('@');
1179+
}
1180+
sb.append(rawHost);
1181+
if (port >= 0) {
1182+
sb.append(':').append(port);
1183+
}
1184+
return sb.toString();
1185+
}
1186+
1187+
private static String buildRawSchemeSpecificPart(
1188+
final String rawAuthority,
1189+
final String rawPath,
1190+
final String rawQuery) {
1191+
final StringBuilder sb = new StringBuilder();
1192+
if (rawAuthority != null) {
1193+
sb.append("//").append(rawAuthority);
1194+
}
1195+
if (rawPath != null) {
1196+
sb.append(rawPath);
1197+
}
1198+
if (rawQuery != null) {
1199+
sb.append('?').append(rawQuery);
1200+
}
1201+
return sb.toString();
1202+
}
1203+
11301204

11311205
/**
11321206
* Converts this instance to a URI string.

httpcore5/src/main/java/org/apache/hc/core5/net/uri/internal/uris/Rfc3986Parser.java

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.hc.core5.annotation.Contract;
3333
import org.apache.hc.core5.annotation.Internal;
3434
import org.apache.hc.core5.annotation.ThreadingBehavior;
35+
import org.apache.hc.core5.net.InetAddressUtils;
3536
import org.apache.hc.core5.net.uri.Rfc3986Uri;
3637
import org.apache.hc.core5.net.uri.internal.authorities.Ports;
3738
import org.apache.hc.core5.net.uri.internal.utils.Ascii;
@@ -349,24 +350,9 @@ private static void validateIpvFuture(final String s) {
349350
}
350351

351352
private static void validateIpv6Address(final String s) {
352-
if (s.indexOf(":::") >= 0) {
353+
if (!InetAddressUtils.isIPv6(s)) {
353354
throw new IllegalArgumentException("Invalid IPv6 literal");
354355
}
355-
final int dbl = s.indexOf("::");
356-
if (dbl >= 0 && s.indexOf("::", dbl + 2) >= 0) {
357-
throw new IllegalArgumentException("Invalid IPv6 literal");
358-
}
359-
for (int i = 0; i < s.length(); i++) {
360-
final char c = s.charAt(i);
361-
if (c > 0x7F) {
362-
throw new IllegalArgumentException("Non-ASCII character in IPv6 literal");
363-
}
364-
if (!(isHex(c) || c == ':' || c == '.')) {
365-
throw new IllegalArgumentException("Illegal character in IPv6 literal");
366-
}
367-
}
368-
// This is "strict enough" for RFC 3986 IP-literal validation without pulling in InetAddress.
369-
// It rejects obvious junk and enforces the '::' compression constraint.
370356
}
371357

372358
private static boolean isUnreserved(final char c) {
@@ -413,7 +399,6 @@ private static int scanScheme(final char[] a, final int from, final int toExcl)
413399
}
414400

415401
private static int scanUntil(final char[] a, final int from, final int toExcl, final char... stops) {
416-
outer:
417402
for (int i = from; i < toExcl; i++) {
418403
final char c = a[i];
419404
for (final char s : stops) {

httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,30 @@ void testOptimize() throws Exception {
880880
Assertions.assertThrows(URISyntaxException.class, () -> new URIBuilder("http:///../../.././").optimize().build().toASCIIString());
881881
}
882882

883+
@Test
884+
void testOptimizeMutatesSameInstance() throws Exception {
885+
final URIBuilder builder = new URIBuilder("HTTP://www.EXAMPLE.com/a/./b/../c");
886+
final URIBuilder optimized = builder.optimize();
887+
Assertions.assertSame(builder, optimized);
888+
Assertions.assertEquals("http://www.example.com/a/c", builder.build().toASCIIString());
889+
}
890+
891+
@Test
892+
void testOptimizeRootlessOpaquePathUnchangedExceptSchemeCase() throws Exception {
893+
final URIBuilder builder = new URIBuilder("MAILTO:John.Doe@example.com");
894+
builder.optimize();
895+
Assertions.assertEquals("mailto:John.Doe@example.com", builder.build().toString());
896+
}
897+
898+
@Test
899+
void testOptimizeIpvFutureAuthorityWithoutJdkUriRoundTrip() {
900+
final URIBuilder builder = new URIBuilder()
901+
.setScheme("http")
902+
.setSchemeSpecificPart("//[v1.fe80]/a/./b");
903+
builder.optimize();
904+
Assertions.assertEquals("http://[v1.fe80]/a/b", builder.toString());
905+
}
906+
883907
@Test
884908
void testIpv6Host() throws Exception {
885909
final URIBuilder builder = new URIBuilder("https://[::1]:432/path");

httpcore5/src/test/java/org/apache/hc/core5/net/uri/Rfc3986UriTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import static org.junit.jupiter.api.Assertions.assertEquals;
3030
import static org.junit.jupiter.api.Assertions.assertNull;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
3132
import static org.junit.jupiter.api.Assertions.assertTrue;
3233

3334
import org.apache.hc.core5.net.uri.internal.paths.DotSegments;
@@ -71,6 +72,11 @@ void parseIpv6Literal() {
7172
assertEquals("/a", u.getPath());
7273
}
7374

75+
@Test
76+
void rejectInvalidIpv6Literal() {
77+
assertThrows(IllegalArgumentException.class, () -> Rfc3986Uri.parse("http://[2001:::1]/a"));
78+
}
79+
7480
@Test
7581
void parseAuthorityLessRelative() {
7682
final Rfc3986Uri u = Rfc3986Uri.parse("a/b?c#d");

0 commit comments

Comments
 (0)