From e20bee27e959d2743c3d6929723d781b80a4354a Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 29 May 2023 13:41:00 +0800 Subject: [PATCH] Added a polyfill for Url::to_file_path() --- .../runtime/package_loader/builtin_loader.rs | 21 ++++++--- lib/wasi/src/runtime/resolver/utils.rs | 45 +++++++++++++++++-- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/wasi/src/runtime/package_loader/builtin_loader.rs b/lib/wasi/src/runtime/package_loader/builtin_loader.rs index c6f44d97922..627b2e2cfb7 100644 --- a/lib/wasi/src/runtime/package_loader/builtin_loader.rs +++ b/lib/wasi/src/runtime/package_loader/builtin_loader.rs @@ -89,13 +89,20 @@ impl BuiltinPackageLoader { async fn download(&self, dist: &DistributionInfo) -> Result { if dist.webc.scheme() == "file" { - // Note: The Url::to_file_path() method is platform-specific - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] - if let Ok(path) = dist.webc.to_file_path() { - // FIXME: This will block the thread - let bytes = std::fs::read(&path) - .with_context(|| format!("Unable to read \"{}\"", path.display()))?; - return Ok(bytes.into()); + match crate::runtime::resolver::utils::file_path_from_url(&dist.webc) { + Ok(path) => { + // FIXME: This will block the thread + let bytes = std::fs::read(&path) + .with_context(|| format!("Unable to read \"{}\"", path.display()))?; + return Ok(bytes.into()); + } + Err(e) => { + tracing::debug!( + url=%dist.webc, + error=&*e, + "Unable to convert the file:// URL to a path", + ); + } } } diff --git a/lib/wasi/src/runtime/resolver/utils.rs b/lib/wasi/src/runtime/resolver/utils.rs index 521215b2cb2..152c93ead21 100644 --- a/lib/wasi/src/runtime/resolver/utils.rs +++ b/lib/wasi/src/runtime/resolver/utils.rs @@ -1,7 +1,7 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; -use anyhow::Error; use http::{HeaderMap, StatusCode}; +use anyhow::{Context, Error}; use url::Url; use crate::http::{HttpResponse, USER_AGENT}; @@ -56,13 +56,39 @@ pub(crate) fn http_error(response: &HttpResponse) -> Error { Error::msg(status) } +pub(crate) fn file_path_from_url(url: &Url) -> Result { + debug_assert_eq!(url.scheme(), "file"); + + // Note: The Url::to_file_path() method is platform-specific + cfg_if::cfg_if! { + if #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] { + if let Ok(path) = url.to_file_path() { + return Ok(path); + } + + // Sometimes we'll get a UNC-like path (e.g. + // "file:///?\\C:/\\/path/to/file.txt") and Url::to_file_path() + // won't be able to handle the "\\?" so we try to "massage" the URL + // a bit. + // See for more. + let modified = url.as_str().replace(r"\\?", "").replace("//?", "").replace('\\', "/"); + Url::parse(&modified) + .ok() + .and_then(|url| url.to_file_path().ok()) + .context("Unable to extract the file path") + } else { + anyhow::bail!("Url::to_file_path() is not supported on this platform"); + } + } +} + #[cfg(test)] mod tests { use super::*; #[test] #[cfg(unix)] - fn behaviour_is_identical() { + fn from_file_path_behaviour_is_identical() { let inputs = [ "/", "/path", @@ -78,4 +104,17 @@ mod tests { assert_eq!(got, expected, "Mismatch for \"{path}\""); } } + + #[test] + #[cfg(windows)] + fn to_file_path_can_handle_unc_paths() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .canonicalize() + .unwrap(); + let url = Url::from_file_path(&path).unwrap(); + + let got = file_path_from_url(&url).unwrap(); + + assert_eq!(got.canonicalize().unwrap(), path); + } }