Skip to content

Commit e871c55

Browse files
authored
Merge pull request #2280 from GitoxideLabs/copilot/add-tests-for-user-password-encoding
fix: prevent dots from being URL-encoded in userinfo
2 parents f8435ea + 87048a3 commit e871c55

File tree

5 files changed

+68
-8
lines changed

5 files changed

+68
-8
lines changed

gix-url/src/lib.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,6 @@ impl Url {
328328
}
329329
}
330330

331-
fn percent_encode(s: &str) -> Cow<'_, str> {
332-
percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).into()
333-
}
334-
335331
/// Serialization
336332
impl Url {
337333
/// Write this URL losslessly to `out`, ready to be parsed again.
@@ -350,6 +346,38 @@ impl Url {
350346
}
351347

352348
fn write_canonical_form_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
349+
fn percent_encode(s: &str) -> Cow<'_, str> {
350+
/// Characters that must be percent-encoded in the userinfo component of a URL.
351+
///
352+
/// According to RFC 3986, userinfo can contain:
353+
/// - unreserved characters: `A-Z a-z 0-9 - . _ ~`
354+
/// - percent-encoded characters
355+
/// - sub-delims: `! $ & ' ( ) * + , ; =`
356+
/// - `:`
357+
///
358+
/// This encode-set encodes everything else, particularly `@` (userinfo delimiter),
359+
/// `/` `?` `#` (path/query/fragment delimiters), and various other special characters.
360+
const USERINFO_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
361+
.add(b' ')
362+
.add(b'"')
363+
.add(b'#')
364+
.add(b'%')
365+
.add(b'/')
366+
.add(b'<')
367+
.add(b'>')
368+
.add(b'?')
369+
.add(b'@')
370+
.add(b'[')
371+
.add(b'\\')
372+
.add(b']')
373+
.add(b'^')
374+
.add(b'`')
375+
.add(b'{')
376+
.add(b'|')
377+
.add(b'}');
378+
percent_encoding::utf8_percent_encode(s, USERINFO_ENCODE_SET).into()
379+
}
380+
353381
out.write_all(self.scheme.as_str().as_bytes())?;
354382
out.write_all(b"://")?;
355383
match (&self.user, &self.host) {

gix-url/tests/fixtures/make_baseline.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ tests_windows=()
1515
for path in "repo" "re:po" "re/po"; do
1616
# normal urls
1717
for protocol in "ssh+git" "git+ssh" "git" "ssh"; do
18-
for host in "host" "user@host" "user_name@host" "user@[::1]" "user@::1"; do
18+
for host in "host" "user@host" "user_name@host" "user.name@host" "user@[::1]" "user@::1"; do
1919
for port_separator in "" ":"; do
2020
tests+=("$protocol://$host$port_separator/$path")
2121

gix-url/tests/url/baseline.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ fn run() {
8484
}
8585

8686
assert!(
87-
failure_count_reserialization <= 63,
87+
failure_count_reserialization <= 72,
8888
"the number of reserialization errors should ideally get better, not worse - if this panic is not due to regressions but to new passing test cases, you can set this check to {failure_count_reserialization}"
8989
);
9090
assert_eq!(failure_count_roundtrips, 0, "there should be no roundtrip errors");
@@ -185,8 +185,8 @@ mod baseline {
185185

186186
pub fn max_num_failures(&self) -> usize {
187187
match self {
188-
Kind::Unix => 195,
189-
Kind::Windows => 195 + 6,
188+
Kind::Unix => 222,
189+
Kind::Windows => 222 + 6,
190190
}
191191
}
192192

gix-url/tests/url/parse/http.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,27 @@ fn http_missing_path() -> crate::Result {
9494
assert_url("http://host.xz", url(Scheme::Http, None, "host.xz", None, b"/"))?;
9595
Ok(())
9696
}
97+
98+
#[test]
99+
fn username_with_dot_is_not_percent_encoded() -> crate::Result {
100+
assert_url_roundtrip(
101+
"http://[email protected]/repo",
102+
url(Scheme::Http, "user.name", "example.com", None, b"/repo"),
103+
)
104+
}
105+
106+
#[test]
107+
fn password_with_dot_is_not_percent_encoded() -> crate::Result {
108+
assert_url_roundtrip(
109+
"http://user:[email protected]/repo",
110+
url_with_pass(Scheme::Http, "user", "pass.word", "example.com", None, b"/repo"),
111+
)
112+
}
113+
114+
#[test]
115+
fn username_and_password_with_dots_are_not_percent_encoded() -> crate::Result {
116+
assert_url_roundtrip(
117+
"http://user.name:[email protected]/repo",
118+
url_with_pass(Scheme::Http, "user.name", "pass.word", "example.com", None, b"/repo"),
119+
)
120+
}

gix-url/tests/url/parse/ssh.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ fn with_user_and_without_port() -> crate::Result {
5555
)
5656
}
5757

58+
#[test]
59+
fn username_with_dot_is_not_percent_encoded() -> crate::Result {
60+
assert_url_roundtrip(
61+
"ssh://[email protected]/.git",
62+
url(Scheme::Ssh, "user.name", "host.xz", None, b"/.git"),
63+
)
64+
}
65+
5866
#[test]
5967
fn with_user_and_port_and_absolute_path() -> crate::Result {
6068
assert_url_roundtrip(

0 commit comments

Comments
 (0)