diff --git a/lib/wasi/src/runtime/package_loader/builtin_loader.rs b/lib/wasi/src/runtime/package_loader/builtin_loader.rs index 20b4da26a78..46532b5da7d 100644 --- a/lib/wasi/src/runtime/package_loader/builtin_loader.rs +++ b/lib/wasi/src/runtime/package_loader/builtin_loader.rs @@ -89,12 +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::polyfills::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/polyfills.rs b/lib/wasi/src/runtime/resolver/polyfills.rs index 7f7614ab6a1..e5cc684d73a 100644 --- a/lib/wasi/src/runtime/resolver/polyfills.rs +++ b/lib/wasi/src/runtime/resolver/polyfills.rs @@ -1,5 +1,6 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; +use anyhow::{Context, Error}; use url::Url; /// Polyfill for [`Url::from_file_path()`] that works on `wasm32-unknown-unknown`. @@ -25,13 +26,39 @@ pub(crate) fn url_from_file_path(path: impl AsRef) -> Option { buffer.parse().ok() } +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(r"\", "/"); + 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", @@ -47,4 +74,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); + } }