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

Restrict #egg= fragments to valid PEP 508 names #11617

Merged
merged 12 commits into from
Dec 28, 2022
11 changes: 9 additions & 2 deletions docs/html/topics/vcs-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,16 @@ option.
pip looks at 2 fragments for VCS URLs:

- `egg`: For specifying the "project name" for use in pip's dependency
resolution logic. eg: `egg=project_name`
resolution logic. e.g.: `egg=project_name`

The `egg` fragment may additionally contain an extras specifier, e.g.:
`egg=project_name[dev,test]`.

Both the project name and extras specifier must appear in the form
defined by [PEP 508](https://peps.python.org/pep-0508/).
woodruffw marked this conversation as resolved.
Show resolved Hide resolved

- `subdirectory`: For specifying the path to the Python package, when it is not
in the root of the VCS directory. eg: `pkg_dir`
in the root of the VCS directory. e.g.: `pkg_dir`

````{admonition} Example
If your repository layout is:
Expand Down
8 changes: 8 additions & 0 deletions src/pip/_internal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,3 +658,11 @@ def __str__(self) -> str:
assert self.error is not None
message_part = f".\n{self.error}\n"
return f"Configuration file {self.reason}{message_part}"


class InvalidEggFragment(InstallationError):
"""A link's `#egg=` fragment doesn't look like a valid PEP 508 project
name."""

def __init__(self, fragment: str) -> None:
super().__init__(f"egg fragment is not a bare project name: {fragment}")
18 changes: 17 additions & 1 deletion src/pip/_internal/models/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
Union,
)

from pip._vendor.packaging.requirements import EXTRAS, NAME
from pip._vendor.pyparsing import Optional as Maybe
from pip._vendor.pyparsing import ParseException, stringEnd, stringStart

from pip._internal.exceptions import InvalidEggFragment
from pip._internal.utils.filetypes import WHEEL_EXTENSION
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
Expand Down Expand Up @@ -358,12 +363,23 @@ def url_without_fragment(self) -> str:

_egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")

_fragment_parser = stringStart + NAME + Maybe(EXTRAS) + stringEnd
woodruffw marked this conversation as resolved.
Show resolved Hide resolved

@property
def egg_fragment(self) -> Optional[str]:
match = self._egg_fragment_re.search(self._url)
if not match:
return None
return match.group(1)

# An egg fragment looks like a PEP 508 project name, along with
# an optional extras specifier. Anything else is invalid.
project_name = match.group(1)
try:
self._fragment_parser.parseString(project_name)
except ParseException:
raise InvalidEggFragment(project_name)

return project_name

_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")

Expand Down