Propagate URL errors in verbatim parsing (#3720)

## Summary

Closes https://github.com/astral-sh/uv/issues/3715.

## Test Plan

```
❯ echo "/../test" | cargo run pip compile -
error: Couldn't parse requirement in `-` at position 0
  Caused by: path could not be normalized: /../test
/../test
^^^^^^^^

❯ echo "-e /../test" | cargo run pip compile -
error: Invalid URL in `-`: `/../test`
  Caused by: path could not be normalized: /../test
  Caused by: cannot normalize a relative path beyond the base directory
```
This commit is contained in:
Charlie Marsh 2024-05-21 15:58:59 -04:00 committed by GitHub
parent 19df1a4372
commit 558f628ef1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 97 additions and 40 deletions

View file

@ -236,6 +236,12 @@ fn preprocess_unnamed_url(
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
let url = VerbatimUrl::parse_path(path.as_ref(), working_dir)
.map_err(|err| Pep508Error {
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
start,
len,
input: cursor.to_string(),
})?
.with_given(url.to_string());
return Ok((url, extras));
}
@ -270,6 +276,12 @@ fn preprocess_unnamed_url(
_ => {
if let Some(working_dir) = working_dir {
let url = VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
.map_err(|err| Pep508Error {
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
start,
len,
input: cursor.to_string(),
})?
.with_given(url.to_string());
return Ok((url, extras));
}
@ -288,8 +300,14 @@ fn preprocess_unnamed_url(
} else {
// Ex) `../editable/`
if let Some(working_dir) = working_dir {
let url =
VerbatimUrl::parse_path(expanded.as_ref(), working_dir).with_given(url.to_string());
let url = VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
.map_err(|err| Pep508Error {
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
start,
len,
input: cursor.to_string(),
})?
.with_given(url.to_string());
return Ok((url, extras));
}

View file

@ -38,25 +38,26 @@ impl VerbatimUrl {
/// Create a [`VerbatimUrl`] from a file path.
///
/// Assumes that the path is absolute.
pub fn from_path(path: impl AsRef<Path>) -> Self {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, VerbatimUrlError> {
let path = path.as_ref();
// Normalize the path.
let path = normalize_path(path).expect("path is absolute");
let path = normalize_path(path)
.map_err(|err| VerbatimUrlError::Normalization(path.to_path_buf(), err))?;
// Extract the fragment, if it exists.
let (path, fragment) = split_fragment(&path);
// Convert to a URL.
let mut url = Url::from_file_path(path.clone())
.unwrap_or_else(|_| panic!("path is absolute: {}", path.display()));
.map_err(|_| VerbatimUrlError::UrlConversion(path.to_path_buf()))?;
// Set the fragment, if it exists.
if let Some(fragment) = fragment {
url.set_fragment(Some(fragment));
}
Self { url, given: None }
Ok(Self { url, given: None })
}
/// Parse a URL from a string, expanding any environment variables.
@ -67,7 +68,10 @@ impl VerbatimUrl {
/// Parse a URL from an absolute or relative path.
#[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs.
pub fn parse_path(path: impl AsRef<Path>, working_dir: impl AsRef<Path>) -> Self {
pub fn parse_path(
path: impl AsRef<Path>,
working_dir: impl AsRef<Path>,
) -> Result<Self, VerbatimUrlError> {
let path = path.as_ref();
// Convert the path to an absolute path, if necessary.
@ -78,26 +82,22 @@ impl VerbatimUrl {
};
// Normalize the path.
let path = normalize_path(&path).expect("path is absolute");
let path = normalize_path(&path)
.map_err(|err| VerbatimUrlError::Normalization(path.to_path_buf(), err))?;
// Extract the fragment, if it exists.
let (path, fragment) = split_fragment(&path);
// Convert to a URL.
let mut url = Url::from_file_path(path.clone()).unwrap_or_else(|_| {
panic!(
"path is absolute: {}, {}",
path.display(),
working_dir.as_ref().display()
)
});
let mut url = Url::from_file_path(path.clone())
.map_err(|_| VerbatimUrlError::UrlConversion(path.to_path_buf()))?;
// Set the fragment, if it exists.
if let Some(fragment) = fragment {
url.set_fragment(Some(fragment));
}
Self { url, given: None }
Ok(Self { url, given: None })
}
/// Parse a URL from an absolute path.
@ -108,12 +108,12 @@ impl VerbatimUrl {
let path = if path.is_absolute() {
path.to_path_buf()
} else {
return Err(VerbatimUrlError::RelativePath(path.to_path_buf()));
return Err(VerbatimUrlError::WorkingDirectory(path.to_path_buf()));
};
// Normalize the path.
let Ok(path) = normalize_path(&path) else {
return Err(VerbatimUrlError::RelativePath(path));
return Err(VerbatimUrlError::WorkingDirectory(path));
};
// Extract the fragment, if it exists.
@ -226,7 +226,7 @@ impl Pep508Url for VerbatimUrl {
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(VerbatimUrl::parse_path(path.as_ref(), working_dir)
return Ok(VerbatimUrl::parse_path(path.as_ref(), working_dir)?
.with_given(url.to_string()));
}
@ -245,7 +245,7 @@ impl Pep508Url for VerbatimUrl {
_ => {
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)?
.with_given(url.to_string()));
}
@ -257,7 +257,7 @@ impl Pep508Url for VerbatimUrl {
// Ex) `../editable/`
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)?
.with_given(url.to_string()));
}
@ -275,7 +275,15 @@ pub enum VerbatimUrlError {
/// Received a relative path, but no working directory was provided.
#[error("relative path without a working directory: {0}")]
RelativePath(PathBuf),
WorkingDirectory(PathBuf),
/// Received a path that could not be converted to a URL.
#[error("path could not be converted to a URL: {0}")]
UrlConversion(PathBuf),
/// Received a path that could not be normalized.
#[error("path could not be normalized: {0}")]
Normalization(PathBuf, #[source] std::io::Error),
}
/// Expand all available environment variables.