From c52b15d61773c95dd29d3b7ba1e18ca627c347be Mon Sep 17 00:00:00 2001
From: Sean Kim <40130483+theskim@users.noreply.github.com>
Date: Wed, 27 Nov 2024 10:42:24 -0500
Subject: [PATCH] Normalize URL paths: convert /.//p, /..//p, and //p to p
---
url/src/lib.rs | 53 +++++++++++++++++++++++++++++++++
url/tests/expected_failures.txt | 4 ---
2 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/url/src/lib.rs b/url/src/lib.rs
index 3eacded1..a74062ff 100644
--- a/url/src/lib.rs
+++ b/url/src/lib.rs
@@ -1757,6 +1757,39 @@ impl Url {
let old_after_path_pos = to_u32(self.serialization.len()).unwrap();
let cannot_be_a_base = self.cannot_be_a_base();
let scheme_type = SchemeType::from(self.scheme());
+ let mut path_empty = false;
+
+ // Check ':' and then see if the next character is '/'
+ let mut has_host = if let Some(index) = self.serialization.find(":") {
+ if self.serialization.len() > index + 1
+ && self.serialization.as_bytes().get(index + 1) == Some(&b'/')
+ {
+ let rest = &self.serialization[(index + ":/".len())..];
+ let host_part = rest.split('/').next().unwrap_or("");
+ path_empty = rest.is_empty();
+ !host_part.is_empty() && !host_part.contains('@')
+ } else {
+ false
+ }
+ } else {
+ false
+ };
+
+ // Ensure the path length is greater than 1 to account
+ // for cases where "/." is already appended from serialization
+ // If we set path, then we already checked the other two conditions:
+ // https://url.spec.whatwg.org/#url-serializing
+ // 1. The host is null
+ // 2. the first segment of the URL's path is an empty string
+ if path.len() > 1 {
+ if let Some(index) = self.serialization.find(":") {
+ let removal_start = index + ":".len();
+ if self.serialization[removal_start..].starts_with("/.") {
+ self.path_start -= "/.".len() as u32;
+ }
+ }
+ }
+
self.serialization.truncate(self.path_start as usize);
self.mutate(|parser| {
if cannot_be_a_base {
@@ -1774,6 +1807,26 @@ impl Url {
);
}
});
+
+ // For cases where normalization is applied across both the serialization and the path.
+ // Append "/." immediately after the scheme (up to ":")
+ // This is done if three conditions are met.
+ // https://url.spec.whatwg.org/#url-serializing
+ // 1. The host is null
+ // 2. The url's path length is greater than 1
+ // 3. the first segment of the URL's path is an empty string
+ if !has_host && path.len() > 1 && path_empty {
+ if let Some(index) = self.serialization.find(":") {
+ if self.serialization.len() > index + 2
+ && self.serialization.as_bytes().get(index + 1) == Some(&b'/')
+ && self.serialization.as_bytes().get(index + 2) == Some(&b'/')
+ {
+ self.serialization.insert_str(index + ":".len(), "/.");
+ self.path_start += "/.".len() as u32;
+ }
+ }
+ }
+
self.restore_after_path(old_after_path_pos, &after_path);
}
diff --git a/url/tests/expected_failures.txt b/url/tests/expected_failures.txt
index 238f7a66..9bf60b34 100644
--- a/url/tests/expected_failures.txt
+++ b/url/tests/expected_failures.txt
@@ -41,7 +41,3 @@
set pathname to <\\\\>
set pathname to /\\/>
set pathname to /monkey/..//>
- set pathname to
- set pathname to
- set pathname to /p>
- set pathname to