Skip to content

Commit

Permalink
pip_api: don't pass escaped path into _parse_local_package_name (#208)
Browse files Browse the repository at this point in the history
* pip_api: don't pass escaped path into _parse_local_package_name

Signed-off-by: William Woodruff <william@trailofbits.com>

* tests: add URL escaped path test

Signed-off-by: William Woodruff <william@trailofbits.com>

---------

Signed-off-by: William Woodruff <william@trailofbits.com>
  • Loading branch information
woodruffw committed Feb 15, 2024
1 parent ad54d28 commit 246cf02
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
21 changes: 10 additions & 11 deletions pip_api/_parse_requirements.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import argparse
import ast
import os
import re
import traceback
import posixpath
import re
import string
import sys

import traceback
from collections import defaultdict

from typing import Any, Dict, Optional, Union

from urllib.parse import urljoin, unquote, urlsplit
from urllib.parse import unquote, urljoin, urlsplit
from urllib.request import pathname2url, url2pathname

from pip_api._vendor import tomli
from pip_api._vendor.packaging import requirements, specifiers # type: ignore

from pip_api.exceptions import PipError

parser = argparse.ArgumentParser()
Expand Down Expand Up @@ -293,18 +289,21 @@ def _parse_editable(editable_req):

# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)
original_url = url_no_extras

if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, "setup.py")):
if os.path.isdir(original_url):
if not os.path.exists(os.path.join(original_url, "setup.py")):
raise PipError(
"Directory %r is not installable. File 'setup.py' not found."
% url_no_extras
% original_url
)
# Treating it as code that has already been checked out
url_no_extras = _path_to_url(url_no_extras)

if url_no_extras.lower().startswith("file:"):
return _parse_local_package_name(url_no_extras[len("file://") :]), url_no_extras
# NOTE: url_no_extras may contain escaped characters here, meaning that
# it may no longer be a literal package path. So we pass original_url.
return _parse_local_package_name(original_url), url_no_extras

if "+" not in url:
raise PipError(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[project]
name = "dummyproject_pyproject"
3 changes: 3 additions & 0 deletions tests/data/escapable@path/dummyproject_pyproject/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from setuptools import setup

setup()
16 changes: 16 additions & 0 deletions tests/test_parse_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ def test_parse_requirements_editable_pyprojecttoml(monkeypatch, data):
)


def test_parse_requirements_editable_escaped_path(monkeypatch, data):
files = {
"a.txt": [f"-e {data.join('escapable@path/dummyproject_pyproject')}\n"],
}
monkeypatch.setattr(pip_api._parse_requirements, "_read_file", files.get)

result = pip_api.parse_requirements("a.txt")

assert set(result) == {"dummyproject_pyproject"}
assert str(result["dummyproject_pyproject"]).startswith(
"dummyproject_pyproject@ file:///"
)
# The @ in `escapable@path` should be URL-encoded
assert "escapable%40path" in str(result["dummyproject_pyproject"])


def test_parse_requirements_with_relative_references(monkeypatch):
files = {
"reqs/base.txt": ["django==1.11\n"],
Expand Down

0 comments on commit 246cf02

Please sign in to comment.