Skip to content

Commit 7ed5977

Browse files
committed
Handle updating the hostname when the first path segment is empty
1 parent 21cada8 commit 7ed5977

File tree

4 files changed

+47
-3
lines changed

4 files changed

+47
-3
lines changed

url/src/lib.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ pub struct Url {
246246
path_start: u32, // Before initial '/', if any
247247
query_start: Option<u32>, // Before '?', unlike Position::QueryStart
248248
fragment_start: Option<u32>, // Before '#', unlike Position::FragmentStart
249+
empty_first_segment: bool,
249250
}
250251

251252
/// Full configuration for the URL parser.
@@ -2124,12 +2125,25 @@ impl Url {
21242125

21252126
/// opt_new_port: None means leave unchanged, Some(None) means remove any port number.
21262127
fn set_host_internal(&mut self, host: Host<String>, opt_new_port: Option<Option<u16>>) {
2128+
// Check if the first segment is empty and there are at least two segments
2129+
// Update self.empty_first_segment flag since the hostname overwrites the blank segment
2130+
if self
2131+
.path_segments()
2132+
.map(|mut segments| {
2133+
let empty_first_segment = segments.next().map_or(false, |first| first.is_empty());
2134+
empty_first_segment && segments.next().is_some()
2135+
})
2136+
.unwrap_or(false)
2137+
{
2138+
self.empty_first_segment = true;
2139+
}
2140+
21272141
let old_suffix_pos = if opt_new_port.is_some() {
21282142
self.path_start
21292143
} else {
21302144
self.host_end
21312145
};
2132-
let suffix = self.slice(old_suffix_pos..).to_owned();
2146+
let mut suffix = self.slice(old_suffix_pos..).to_owned();
21332147
self.serialization.truncate(self.host_start as usize);
21342148
if !self.has_authority() {
21352149
debug_assert!(self.slice(self.scheme_end..self.host_start) == ":");
@@ -2143,6 +2157,23 @@ impl Url {
21432157
self.host_end = to_u32(self.serialization.len()).unwrap();
21442158
self.host = host.into();
21452159

2160+
// Adjust serialization to switch between host and empty segment
2161+
if self.empty_first_segment {
2162+
if suffix.starts_with("/.//") {
2163+
suffix = suffix["/.".len()..].to_string();
2164+
} else if self.host == HostInternal::None && suffix.starts_with("/") {
2165+
if let Some(index) = self.serialization.find(":") {
2166+
if self.serialization.len() == index + "://".len()
2167+
&& self.serialization.as_bytes().get(index + 1) == Some(&b'/')
2168+
&& self.serialization.as_bytes().get(index + 2) == Some(&b'/')
2169+
{
2170+
self.serialization
2171+
.replace_range(index..index + "://".len(), ":/.");
2172+
}
2173+
}
2174+
}
2175+
}
2176+
21462177
if let Some(new_port) = opt_new_port {
21472178
self.port = new_port;
21482179
if let Some(port) = new_port {
@@ -2157,6 +2188,10 @@ impl Url {
21572188
*index += new_suffix_pos;
21582189
};
21592190
adjust(&mut self.path_start);
2191+
// pathname should be "//p" not "p" given that the first segment was empty
2192+
if self.empty_first_segment {
2193+
self.path_start -= "//".len() as u32;
2194+
}
21602195
if let Some(ref mut index) = self.query_start {
21612196
adjust(index)
21622197
}
@@ -2605,6 +2640,7 @@ impl Url {
26052640
path_start: host_end,
26062641
query_start: None,
26072642
fragment_start: None,
2643+
empty_first_segment: false,
26082644
})
26092645
}
26102646

url/src/parser.rs

+5
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ impl<'a> Parser<'a> {
554554
path_start: host_end,
555555
query_start,
556556
fragment_start,
557+
empty_first_segment: false,
557558
});
558559
} else {
559560
self.serialization.push_str("file://");
@@ -605,6 +606,7 @@ impl<'a> Parser<'a> {
605606
path_start: host_end,
606607
query_start,
607608
fragment_start,
609+
empty_first_segment: false,
608610
});
609611
}
610612
}
@@ -685,6 +687,7 @@ impl<'a> Parser<'a> {
685687
path_start,
686688
query_start,
687689
fragment_start,
690+
empty_first_segment: false,
688691
})
689692
}
690693
}
@@ -708,6 +711,7 @@ impl<'a> Parser<'a> {
708711
path_start,
709712
query_start,
710713
fragment_start,
714+
empty_first_segment: false,
711715
})
712716
}
713717
}
@@ -1436,6 +1440,7 @@ impl<'a> Parser<'a> {
14361440
path_start,
14371441
query_start,
14381442
fragment_start,
1443+
empty_first_segment: false,
14391444
})
14401445
}
14411446

url/tests/expected_failures.txt

-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636
<file:/.//p>
3737
<http://example.net/path> set hostname to <example.com:8080>
3838
<http://example.net:8080/path> set hostname to <example.com:>
39-
<non-spec:/.//p> set hostname to <h>
40-
<non-spec:/.//p> set hostname to <>
4139
<foo:///some/path> set pathname to <>
4240
<file:///var/log/system.log> set href to <http://0300.168.0xF0>
4341
<file://monkey/> set pathname to <\\\\>

url/tests/unit.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,7 @@ fn serde_error_message() {
13821382

13831383
#[test]
13841384
fn test_can_be_a_base_with_set_path() {
1385+
use url::quirks;
13851386
let mut url = Url::parse("web+demo:/").unwrap();
13861387
assert!(!url.cannot_be_a_base());
13871388

@@ -1396,6 +1397,10 @@ fn test_can_be_a_base_with_set_path() {
13961397
assert_eq!(segments, vec!["", "not-a-host"]);
13971398

13981399
assert_eq!(url.as_str(), "web+demo:/.//not-a-host");
1400+
quirks::set_hostname(&mut url, "test").unwrap();
1401+
assert_eq!(url.as_str(), "web+demo://test//not-a-host");
1402+
quirks::set_hostname(&mut url, "").unwrap();
1403+
assert_eq!(url.as_str(), "web+demo:/.//not-a-host");
13991404
}
14001405

14011406
#[test]

0 commit comments

Comments
 (0)