fix: do not panic running invalid file specifier (#25530)

Co-authored-by: Bedis Nbiba <bedisnbiba@gmail.com>
This commit is contained in:
Yazan AbdAl-Rahman 2024-09-18 16:51:39 +03:00 committed by GitHub
parent 48ea4e3c92
commit bed46474b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 146 additions and 103 deletions

View file

@ -20,6 +20,7 @@ fqdn = "0.3.4"
libc.workspace = true
log.workspace = true
once_cell.workspace = true
percent-encoding = { version = "2.3.1", features = [] }
serde.workspace = true
which.workspace = true

View file

@ -1932,7 +1932,7 @@ impl Permissions {
specifier: &ModuleSpecifier,
) -> Result<(), AnyError> {
match specifier.scheme() {
"file" => match specifier.to_file_path() {
"file" => match specifier_to_file_path(specifier) {
Ok(path) => self.read.check(
&PathQueryDescriptor {
requested: path.to_string_lossy().into_owned(),
@ -1952,6 +1952,52 @@ impl Permissions {
}
}
/// Attempts to convert a specifier to a file path. By default, uses the Url
/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
/// paths on Windows.
pub fn specifier_to_file_path(
specifier: &ModuleSpecifier,
) -> Result<PathBuf, AnyError> {
let result = if specifier.scheme() != "file" {
Err(())
} else if cfg!(windows) {
if specifier.host().is_some() {
Err(())
} else {
match specifier.to_file_path() {
Ok(path) => Ok(path),
Err(()) => {
// This might be a unix-style path which is used in the tests even on Windows.
// Attempt to see if we can convert it to a `PathBuf`. This code should be removed
// once/if https://github.com/servo/rust-url/issues/730 is implemented.
if specifier.scheme() == "file"
&& specifier.port().is_none()
&& specifier.path_segments().is_some()
{
let path_str = specifier.path();
match String::from_utf8(
percent_encoding::percent_decode(path_str.as_bytes()).collect(),
) {
Ok(path_str) => Ok(PathBuf::from(path_str)),
Err(_) => Err(()),
}
} else {
Err(())
}
}
}
}
} else {
specifier.to_file_path()
};
match result {
Ok(path) => Ok(path),
Err(()) => Err(uri_error(format!(
"Invalid file path.\n Specifier: {specifier}"
))),
}
}
/// Wrapper struct for `Permissions` that can be shared across threads.
///
/// We need a way to have internal mutability for permissions as they might get
@ -3228,12 +3274,9 @@ mod tests {
let mut test_cases = vec![];
if cfg!(target_os = "windows") {
test_cases.push("file://");
test_cases.push("file:///");
} else {
test_cases.push("file://remotehost/");
}
test_cases.push("file://dir");
test_cases.push("file://asdf/");
test_cases.push("file://remotehost/");
for url in test_cases {
assert!(perms
@ -4222,4 +4265,38 @@ mod tests {
);
}
}
#[test]
fn test_specifier_to_file_path() {
run_success_test("file:///", "/");
run_success_test("file:///test", "/test");
run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
run_success_test(
"file:///dir/test%20test/test.txt",
"/dir/test test/test.txt",
);
assert_no_panic_specifier_to_file_path("file:/");
assert_no_panic_specifier_to_file_path("file://");
assert_no_panic_specifier_to_file_path("file://asdf/");
assert_no_panic_specifier_to_file_path("file://asdf/66666/a.ts");
fn run_success_test(specifier: &str, expected_path: &str) {
let result =
specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
.unwrap();
assert_eq!(result, PathBuf::from(expected_path));
}
fn assert_no_panic_specifier_to_file_path(specifier: &str) {
let result =
specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap());
match result {
Ok(_) => (),
Err(err) => assert_eq!(
err.to_string(),
format!("Invalid file path.\n Specifier: {specifier}")
),
}
}
}
}