Skip to content

Commit 06d6ef3

Browse files
lrhnCommit Queue
authored and
Commit Queue
committed
Fix bug in Uri.resolve with IPv6 addresses.
The `resolve`/`resolveUri` operation would take the `.host` of the URI reference and include it verbatim in the output. Since the `.host` getter returns IPv6 addresses *without* their `[`...`]` braces, that would become invalid. Also make sure to normalize the parts of the URI reference that are used, if it is not a platform URI. Fixes #55085 BUG= https://dartbug.com/55085 Change-Id: I3dbc8af953af0974346e38ba3203796647069ea8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/355781 Reviewed-by: Stephen Adams <[email protected]> Commit-Queue: Lasse Nielsen <[email protected]> Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Nate Bosch <[email protected]> Commit-Queue: Martin Kustermann <[email protected]> Auto-Submit: Lasse Nielsen <[email protected]>
1 parent f4c8c9b commit 06d6ef3

File tree

3 files changed

+203
-7
lines changed

3 files changed

+203
-7
lines changed

sdk/lib/core/uri.dart

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,10 @@ abstract interface class Uri {
15071507
}
15081508
}
15091509

1510-
class _Uri implements Uri {
1510+
// Superclass of the two implementation types.
1511+
sealed class _PlatformUri implements Uri {}
1512+
1513+
final class _Uri implements _PlatformUri {
15111514
// We represent the missing scheme as an empty string.
15121515
// A valid scheme cannot be empty.
15131516
final String scheme;
@@ -2705,12 +2708,12 @@ class _Uri implements Uri {
27052708
return resolveUri(Uri.parse(reference));
27062709
}
27072710

2708-
// Returns the index of the `/` after the package name of a package URI.
2711+
// The index of the `/` after the package name of a package URI.
27092712
//
2710-
// Returns negative if the URI is not a valid package URI:
2713+
// Value is negative if the URI is not a valid package URI:
27112714
// * Scheme must be "package".
27122715
// * No authority.
2713-
// * Path starts with "something"/
2716+
// * Path starts with "something/".
27142717
// * where "something" is not all "." characters,
27152718
// * and contains no escapes or colons.
27162719
//
@@ -2730,7 +2733,21 @@ class _Uri implements Uri {
27302733
int? targetPort;
27312734
String targetPath;
27322735
String? targetQuery;
2736+
// Position up to which values are known to already be normalized,
2737+
// because the value is taken from this `_Uri`
2738+
// If any part of the path is taken from a `reference` which is not
2739+
// a platform URI, and therefore not known to be canonicalized to the
2740+
// standard of platform URIs, the combined path counts as potentially
2741+
// non-normalized.
2742+
const int atStart = 0, // Nothing taken from this URI.
2743+
afterScheme = 1, // Scheme comes from this URI.
2744+
afterAuthority = 2, // Scheme and authority comes from this URI.
2745+
afterPath = 3, // The path, and everything before, is from this URI.
2746+
afterQuery = 4; // Everything except fragment is from this URI.
2747+
int split = atStart;
2748+
27332749
if (reference.scheme.isNotEmpty) {
2750+
if (reference is _PlatformUri) return reference;
27342751
targetScheme = reference.scheme;
27352752
if (reference.hasAuthority) {
27362753
targetUserInfo = reference.userInfo;
@@ -2744,26 +2761,33 @@ class _Uri implements Uri {
27442761
} else {
27452762
targetScheme = this.scheme;
27462763
if (reference.hasAuthority) {
2764+
if (reference is _PlatformUri) {
2765+
return reference.replace(scheme: targetScheme);
2766+
}
27472767
targetUserInfo = reference.userInfo;
27482768
targetHost = reference.host;
27492769
targetPort =
27502770
_makePort(reference.hasPort ? reference.port : null, targetScheme);
27512771
targetPath = _removeDotSegments(reference.path);
27522772
if (reference.hasQuery) targetQuery = reference.query;
2773+
split = afterScheme;
27532774
} else {
27542775
targetUserInfo = this._userInfo;
27552776
targetHost = this._host;
27562777
targetPort = this._port;
2757-
if (reference.path == "") {
2778+
if (reference.hasEmptyPath) {
27582779
targetPath = this.path;
27592780
if (reference.hasQuery) {
2781+
split = afterPath;
27602782
targetQuery = reference.query;
27612783
} else {
27622784
targetQuery = this._query;
2785+
split = afterQuery;
27632786
}
27642787
} else {
27652788
String basePath = this.path;
27662789
int packageNameEnd = _packageNameEnd(this, basePath);
2790+
split = afterAuthority;
27672791
if (packageNameEnd > 0) {
27682792
assert(targetScheme == "package");
27692793
assert(!this.hasAuthority);
@@ -2809,11 +2833,41 @@ class _Uri implements Uri {
28092833
}
28102834
}
28112835
}
2812-
if (reference.hasQuery) targetQuery = reference.query;
2836+
if (reference.hasQuery) {
2837+
targetQuery = reference.query;
2838+
}
28132839
}
28142840
}
28152841
}
28162842
String? fragment = reference.hasFragment ? reference.fragment : null;
2843+
if (reference is! _PlatformUri) {
2844+
// Don't trust values coming from `reference` to be normalized.
2845+
if (split == atStart) {
2846+
targetScheme = _makeScheme(targetScheme, 0, targetScheme.length);
2847+
}
2848+
if (split <= afterScheme) {
2849+
if (targetUserInfo != null) {
2850+
targetUserInfo =
2851+
_makeUserInfo(targetUserInfo, 0, targetUserInfo.length);
2852+
}
2853+
if (targetPort != null) {
2854+
targetPort = _makePort(targetPort, targetScheme);
2855+
}
2856+
if (targetHost != null && targetHost.isNotEmpty) {
2857+
targetHost = _makeHost(targetHost, 0, targetHost.length, false);
2858+
}
2859+
}
2860+
if (split <= afterPath) {
2861+
targetPath = _makePath(targetPath, 0, targetPath.length, null,
2862+
targetScheme, targetHost != null);
2863+
}
2864+
if (split <= afterPath && targetQuery != null) {
2865+
targetQuery = _makeQuery(targetQuery, 0, targetQuery.length, null);
2866+
}
2867+
if (fragment != null) {
2868+
fragment = _makeFragment(fragment, 0, fragment.length);
2869+
}
2870+
}
28172871
return _Uri._internal(targetScheme, targetUserInfo, targetHost, targetPort,
28182872
targetPath, targetQuery, fragment);
28192873
}
@@ -4406,7 +4460,7 @@ int _scan(String uri, int start, int end, int state, List<int> indices) {
44064460
return state;
44074461
}
44084462

4409-
class _SimpleUri implements Uri {
4463+
final class _SimpleUri implements _PlatformUri {
44104464
final String _uri;
44114465
final int _schemeEnd;
44124466
final int _hostStart;

tests/corelib/uri_ipv6_test.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,76 @@ void testParseIPv6Address() {
211211
[32, 16, 131, 107, 65, 121, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1]);
212212
}
213213

214+
void testPropagateIPv6() {
215+
// A regression test for https://dartbug.com/55085
216+
217+
// A "Normal" URI. Simple URIs cannot have IPv6 addresses.
218+
var ipv6Uri = Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q#f");
219+
220+
// A non-IPv6 URI.
221+
var plainUri = Uri.parse("s2://u2:p2@host:2/p3/p4?q2#f2");
222+
223+
void expectSame(Uri expected, Uri actual) {
224+
Expect.equals(expected, actual, "URI equality");
225+
Expect.equals(
226+
expected.toString(), actual.toString(), "URI.toString() equality");
227+
}
228+
229+
Expect.equals("s://u:p@[::127.0.0.1]:1/p1/p2?q#f", ipv6Uri.toString());
230+
Expect.equals("::127.0.0.1", ipv6Uri.host);
231+
Expect.equals("u:p", ipv6Uri.userInfo);
232+
Expect.equals(1, ipv6Uri.port);
233+
234+
// Using resolve to change parts of an IPv6 URI.
235+
expectSame(
236+
Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q#f2"), ipv6Uri.resolve("#f2"));
237+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q2#f2"),
238+
ipv6Uri.resolve("?q2#f2"));
239+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p3?q2#f2"),
240+
ipv6Uri.resolve("p3?q2#f2"));
241+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p3/p4?q2#f2"),
242+
ipv6Uri.resolve("/p3/p4?q2#f2"));
243+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p3/p4?q2#f2"),
244+
ipv6Uri.resolve("/p3/p4?q2#f2"));
245+
expectSame(Uri.parse("s://u2:[email protected]:2/p3/p4?q2#f2"),
246+
ipv6Uri.resolve("//u2:[email protected]:2/p3/p4?q2#f2"));
247+
expectSame(Uri.parse("s2://u2:[email protected]:2/p3/p4?q2#f2"),
248+
ipv6Uri.resolve("s2://u2:[email protected]:2/p3/p4?q2#f2"));
249+
250+
// Using resolve to change parts to an IPv6 URI.
251+
expectSame(Uri.parse("s2://u:p@[::127.0.0.1]:1/p1/p2?q#f"),
252+
plainUri.resolve("//u:p@[::127.0.0.1]:1/p1/p2?q#f"));
253+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q#f"),
254+
plainUri.resolveUri(ipv6Uri));
255+
256+
// Using replace to change non-host parts of an IPv6 URI.
257+
expectSame(Uri.parse("s2://u:p@[::127.0.0.1]:1/p1/p2?q#f"),
258+
ipv6Uri.replace(scheme: "s2"));
259+
expectSame(
260+
Uri.parse("s://u:p@[::127.0.0.1]:2/p1/p2?q#f"), ipv6Uri.replace(port: 2));
261+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p3/p4?q#f"),
262+
ipv6Uri.replace(path: "p3/p4"));
263+
expectSame(
264+
Uri.parse("s://u:p@[::127.0.0.1]:1?q#f"), ipv6Uri.replace(path: ""));
265+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q2#f"),
266+
ipv6Uri.replace(query: "q2"));
267+
expectSame(Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q#f2"),
268+
ipv6Uri.replace(fragment: "f2"));
269+
// Replacing the host to or from an IPv6 address.
270+
expectSame(
271+
Uri.parse("s://u:p@host:1/p1/p2?q#f"), ipv6Uri.replace(host: "host"));
272+
expectSame(Uri.parse("s2://u2:p2@[::127.0.0.1]:2/p3/p4?q2#f2"),
273+
plainUri.replace(host: "[::127.0.0.1]"));
274+
expectSame(Uri.parse("s2://u2:p2@[::127.0.0.1]:2/p3/p4?q2#f2"),
275+
plainUri.replace(host: "::127.0.0.1"));
276+
277+
// Removing fragment.
278+
expectSame(
279+
Uri.parse("s://u:p@[::127.0.0.1]:1/p1/p2?q"), ipv6Uri.removeFragment());
280+
}
281+
214282
void main() {
215283
testValidIpv6Uri();
216284
testParseIPv6Address();
285+
testPropagateIPv6();
217286
}

tests/corelib/uri_normalize_test.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,79 @@ testNormalizePath() {
7272
test("./", "a/..");
7373
}
7474

75+
void testNormalizeResolve() {
76+
var uri = Uri.parse("scheme://user:[email protected]:1/path/?query#fragment");
77+
78+
var nonCanon = NonCanonicalizingUri();
79+
80+
nonCanon
81+
..fragment = "fr%61gment"
82+
..hasFragment = true;
83+
Expect.equals(uri, uri.resolveUri(nonCanon));
84+
85+
nonCanon
86+
..query = "qu%65ry"
87+
..hasQuery = true;
88+
Expect.equals(uri, uri.resolveUri(nonCanon));
89+
90+
nonCanon..path = "/p%61th/";
91+
Expect.equals(uri, uri.resolveUri(nonCanon));
92+
nonCanon..path = "../p%61th/";
93+
Expect.equals(uri, uri.resolveUri(nonCanon));
94+
95+
nonCanon
96+
..hasAuthority = true
97+
..hasUserInfo = true
98+
..userInfo = "us%65r:pass"
99+
..host = "ex%41mple.com"
100+
..hasPort = true
101+
..port = 1;
102+
Expect.equals(uri, uri.resolveUri(nonCanon));
103+
104+
nonCanon
105+
..hasScheme = true
106+
..scheme = "schEme";
107+
Expect.equals(uri, uri.resolveUri(nonCanon));
108+
}
109+
75110
main() {
76111
testNormalizePath();
112+
testNormalizeResolve();
113+
}
114+
115+
class NonCanonicalizingUri implements Uri {
116+
String scheme = "";
117+
String userInfo = "";
118+
String host = "";
119+
int port = 0;
120+
String path = "";
121+
String query = "";
122+
String fragment = "";
123+
bool hasScheme = false;
124+
bool hasAuthority = false;
125+
bool hasUserInfo = false;
126+
bool hasPort = false;
127+
bool hasQuery = false;
128+
bool hasFragment = false;
129+
130+
bool get hasEmptyPath => path.isEmpty;
131+
bool get hasAbsolutePath => path.startsWith("/") || isScheme("file");
132+
bool isScheme(String scheme) =>
133+
scheme.toLowerCase() == Uri.decodeComponent(this.scheme).toLowerCase();
134+
135+
Uri normalize() => this; // Na-ah!
136+
137+
List<String> get pathSegments => path.split("/");
138+
139+
String toString() => "${hasScheme ? "$scheme:" : ""}"
140+
"${hasAuthority ? "//${hasUserInfo ? "$userInfo@" : ""}"
141+
"$host"
142+
"${hasPort ? ":$port" : ""}" : ""}"
143+
"$path${hasQuery ? "?$query" : ""}${hasFragment ? "#$fragment" : ""}";
144+
145+
int get hashCode => toString().hashCode;
146+
bool operator ==(Object other) =>
147+
other is Uri && toString() == other.toString();
148+
149+
Object? noSuchMethod(i) => super.noSuchMethod(i);
77150
}

0 commit comments

Comments
 (0)