Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always treat archive-like requirements as local files #7364

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions crates/pep508-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,13 @@ fn looks_like_unnamed_requirement(cursor: &mut Cursor) -> bool {
// Expand any environment variables in the path.
let expanded = expand_env_vars(url);

// Strip extras.
let url = split_extras(&expanded)
.map(|(url, _)| url)
.unwrap_or(&expanded);

// Analyze the path.
let mut chars = expanded.chars();
let mut chars = url.chars();

let Some(first_char) = chars.next() else {
return false;
Expand All @@ -670,18 +675,47 @@ fn looks_like_unnamed_requirement(cursor: &mut Cursor) -> bool {
}

// Ex) `https://` or `C:`
if split_scheme(&expanded).is_some() {
if split_scheme(url).is_some() {
return true;
}

// Ex) `foo/bar`
if expanded.contains('/') || expanded.contains('\\') {
if url.contains('/') || url.contains('\\') {
return true;
}

// Ex) `foo.tar.gz`
if looks_like_archive(url) {
return true;
}

false
}

/// Returns `true` if a file looks like an archive.
///
/// See <https://github.com/pypa/pip/blob/111eed14b6e9fba7c78a5ec2b7594812d17b5d2b/src/pip/_internal/utils/filetypes.py#L8>
/// for the list of supported archive extensions.
fn looks_like_archive(file: impl AsRef<Path>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the DistFilename parsing here or are you intentionally using a wider filter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally a bit wider to match pip.

let file = file.as_ref();

// E.g., `gz` in `foo.tar.gz`
let Some(extension) = file.extension().and_then(|ext| ext.to_str()) else {
return false;
};

// E.g., `tar` in `foo.tar.gz`
let pre_extension = file
.file_stem()
.and_then(|stem| Path::new(stem).extension().and_then(|ext| ext.to_str()));

matches!(
(pre_extension, extension),
(_, "whl" | "tbz" | "txz" | "tlz" | "zip" | "tgz" | "tar")
| (Some("tar"), "bz2" | "xz" | "lz" | "lzma" | "gz")
)
}

/// parses extras in the `[extra1,extra2] format`
fn parse_extras_cursor<T: Pep508Url>(
cursor: &mut Cursor,
Expand Down Expand Up @@ -970,7 +1004,9 @@ fn parse_pep508_requirement<T: Pep508Url>(
// wsp*
cursor.eat_whitespace();
// name
let name_start = cursor.pos();
let name = parse_name(cursor)?;
let name_end = cursor.pos();
// wsp*
cursor.eat_whitespace();
// extras?
Expand Down Expand Up @@ -1018,6 +1054,23 @@ fn parse_pep508_requirement<T: Pep508Url>(

let requirement_end = cursor.pos();

// If the requirement consists solely of a package name, and that name appears to be an archive,
// treat it as a URL requirement, for consistency and security. (E.g., `requests-2.26.0.tar.gz`
// is a valid Python package name, but we should treat it as a reference to a file.)
//
// See: https://github.com/pypa/pip/blob/111eed14b6e9fba7c78a5ec2b7594812d17b5d2b/src/pip/_internal/utils/filetypes.py#L8
if requirement_kind.is_none() {
if looks_like_archive(cursor.slice(name_start, name_end)) {
let clone = cursor.clone().at(start);
return Err(Pep508Error {
message: Pep508ErrorSource::UnsupportedRequirement("URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ https://...`).".to_string()),
start,
len: clone.pos() - start,
input: clone.to_string(),
});
}
}

// wsp*
cursor.eat_whitespace();
// quoted_marker?
Expand Down
Loading
Loading