Skip to content

Commit b0ba159

Browse files
committed
Fix invariants of anarchist URLs + empty fragment
1 parent d74092c commit b0ba159

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

url/src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,14 @@ impl Url {
683683
assert_eq!(self.host_end, self.scheme_end + 1);
684684
assert_eq!(self.host, HostInternal::None);
685685
assert_eq!(self.port, None);
686-
assert_eq!(self.path_start, self.scheme_end + 1);
686+
if self.path().starts_with("//") {
687+
// special case when first path fragment is empty
688+
assert_eq!(self.byte_at(self.scheme_end + 1), b'/');
689+
assert_eq!(self.byte_at(self.scheme_end + 2), b'.');
690+
assert_eq!(self.path_start, self.scheme_end + 3);
691+
} else {
692+
assert_eq!(self.path_start, self.scheme_end + 1);
693+
}
687694
}
688695
if let Some(start) = self.query_start {
689696
assert!(start >= self.path_start);

url/src/parser.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -467,19 +467,28 @@ impl<'a> Parser<'a> {
467467
return self.after_double_slash(input, scheme_type, scheme_end);
468468
}
469469
// Anarchist URL (no authority)
470-
let path_start = to_u32(self.serialization.len())?;
470+
let mut path_start = to_u32(self.serialization.len())?;
471471
let username_end = path_start;
472472
let host_start = path_start;
473473
let host_end = path_start;
474474
let host = HostInternal::None;
475475
let port = None;
476476
let remaining = if let Some(input) = input.split_prefix('/') {
477-
let path_start = self.serialization.len();
478477
self.serialization.push('/');
479-
self.parse_path(scheme_type, &mut false, path_start, input)
478+
self.parse_path(scheme_type, &mut false, path_start as usize, input)
480479
} else {
481480
self.parse_cannot_be_a_base_path(input)
482481
};
482+
// This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/,
483+
// when parsed and then serialized, from ending up as web+demo://not-a-host/
484+
// (they end up as web+demo:/.//not-a-host/).
485+
if self.serialization[path_start as usize..].starts_with("//") {
486+
// If url’s host is null, url does not have an opaque path,
487+
// url’s path’s size is greater than 1, and url’s path[0] is the empty string,
488+
// then append U+002F (/) followed by U+002E (.) to output.
489+
self.serialization.insert_str(path_start as usize, "/.");
490+
path_start += 2;
491+
}
483492
self.with_query_and_fragment(
484493
scheme_type,
485494
scheme_end,
@@ -1281,16 +1290,6 @@ impl<'a> Parser<'a> {
12811290
self.serialization.push_str(path.trim_start_matches('/'));
12821291
}
12831292

1284-
// This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/,
1285-
// when parsed and then serialized, from ending up as web+demo://not-a-host/
1286-
// (they end up as web+demo:/.//not-a-host/).
1287-
if !*has_host && self.serialization[path_start..].starts_with("//") {
1288-
// If url’s host is null, url does not have an opaque path,
1289-
// url’s path’s size is greater than 1, and url’s path[0] is the empty string,
1290-
// then append U+002F (/) followed by U+002E (.) to output.
1291-
self.serialization.insert_str(path_start, "/.");
1292-
}
1293-
12941293
input
12951294
}
12961295

url/src/slicing.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,14 @@ impl Url {
149149
}
150150
}
151151

152-
Position::AfterPort => self.path_start as usize,
152+
Position::AfterPort => {
153+
if let Some(port) = self.port {
154+
debug_assert!(self.byte_at(self.host_end) == b':');
155+
self.host_end as usize + ":".len() + port.to_string().len()
156+
} else {
157+
self.host_end as usize
158+
}
159+
}
153160

154161
Position::BeforePath => self.path_start as usize,
155162

url/tests/urltestdata.json

-8
Original file line numberDiff line numberDiff line change
@@ -7487,7 +7487,6 @@
74877487
"hash": ""
74887488
},
74897489
"Serialize /. in path",
7490-
"skip next",
74917490
{
74927491
"input": "non-spec:/.//",
74937492
"base": "about:blank",
@@ -7502,7 +7501,6 @@
75027501
"search": "",
75037502
"hash": ""
75047503
},
7505-
"skip next",
75067504
{
75077505
"input": "non-spec:/..//",
75087506
"base": "about:blank",
@@ -7517,7 +7515,6 @@
75177515
"search": "",
75187516
"hash": ""
75197517
},
7520-
"skip next",
75217518
{
75227519
"input": "non-spec:/a/..//",
75237520
"base": "about:blank",
@@ -7532,7 +7529,6 @@
75327529
"search": "",
75337530
"hash": ""
75347531
},
7535-
"skip next",
75367532
{
75377533
"input": "non-spec:/.//path",
75387534
"base": "about:blank",
@@ -7547,7 +7543,6 @@
75477543
"search": "",
75487544
"hash": ""
75497545
},
7550-
"skip next",
75517546
{
75527547
"input": "non-spec:/..//path",
75537548
"base": "about:blank",
@@ -7562,7 +7557,6 @@
75627557
"search": "",
75637558
"hash": ""
75647559
},
7565-
"skip next",
75667560
{
75677561
"input": "non-spec:/a/..//path",
75687562
"base": "about:blank",
@@ -7637,7 +7631,6 @@
76377631
"search": "",
76387632
"hash": ""
76397633
},
7640-
"skip next",
76417634
{
76427635
"input": "",
76437636
"base": "non-spec:/..//p",
@@ -7652,7 +7645,6 @@
76527645
"search": "",
76537646
"hash": ""
76547647
},
7655-
"skip next",
76567648
{
76577649
"input": "path",
76587650
"base": "non-spec:/..//p",

0 commit comments

Comments
 (0)