Skip to content

support hash checks for url reqs with hash fragment #735

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

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion pip/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def unpack_vcs_link(link, location, only_download=False):


def unpack_file_url(link, location):
source = url_to_path(link.url)
source = url_to_path(link.url_without_fragment)
content_type = mimetypes.guess_type(source)[0]
if os.path.isdir(source):
# delete the location since shutil will create it again :(
Expand Down
4 changes: 2 additions & 2 deletions pip/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ def url_without_fragment(self):
scheme, netloc, path, query, fragment = urlparse.urlsplit(self.url)
return urlparse.urlunsplit((scheme, netloc, path, query, None))

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

@property
def egg_fragment(self):
Expand All @@ -657,7 +657,7 @@ def egg_fragment(self):
return None
return match.group(1)

_hash_re = re.compile(r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)')
_hash_re = re.compile(r'#[^#]*(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)')

@property
def hash(self):
Expand Down
6 changes: 3 additions & 3 deletions pip/req.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ def from_line(cls, name, comes_from=None):
# If the line has an egg= definition, but isn't editable, pull the requirement out.
# Otherwise, assume the name is the req for the non URL/path/archive case.
if link and req is None:
url = link.url_without_fragment
req = link.egg_fragment #when fragment is None, this will become an 'unnamed' requirement
url = link.url
req = link.egg_fragment #when egg fragment is None, this will become an 'unnamed' requirement

# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', url):
if link.scheme == 'file' and re.search(r'\.\./', link.url_without_fragment):
url = path_to_url(os.path.normpath(os.path.abspath(link.path)))

else:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,33 @@ def test_file_index_url_quoting():
def test_inflink_greater():
"""Test InfLink compares greater."""
assert InfLink > Link(object())


class TestLink:
"""Tests for the pip.index.Link"""

def test_egg_fragment(self):
"""Test Link egg_fragment property."""
url = 'http://test?egg=bogus#egg=test'
assert Link(url).egg_fragment == 'test'
url = 'http://test?egg=bogus#egg=test&md5=123'
assert Link(url).egg_fragment == 'test'
url = 'http://test?egg=bogus#md5=123&egg=test'
assert Link(url).egg_fragment == 'test'

def test_hash_name(self):
"""Test Link hash/hash_name properties."""
url = 'http://test?md5=bogus#md5=123'
assert Link(url).hash_name == 'md5'
assert Link(url).hash == '123'
url = 'http://test?md5=bogus#md5=123&egg=test'
assert Link(url).hash_name == 'md5'
assert Link(url).hash == '123'
url = 'http://test?md5=bogus#egg=test&md5=123'
assert Link(url).hash_name == 'md5'
assert Link(url).hash == '123'

def test_url_without_fragment(self):
"""Test Link url_without_fragment property."""
url = 'http://test#egg=test&md5=123'
assert Link(url).url_without_fragment == 'http://test'
5 changes: 2 additions & 3 deletions tests/test_install_requirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@


def test_url_with_query():
"""InstallRequirement should strip the fragment, but not the query."""
"""InstallRequirement should not strip the query."""
url = 'http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz'
fragment = '#egg=bar'
req = InstallRequirement.from_line(url + fragment)

assert req.url == url, req.url
assert req.url.startswith(url)
24 changes: 24 additions & 0 deletions tests/test_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,28 @@ def test_url_req_case_mismatch():
assert egg_folder not in result.files_created, str(result)


def test_url_req_with_correct_hash_fragment():
"""
Test installing url requirement with correct md5 hash fragment succeeds.
"""
env = reset_env()
tar = 'simple-1.0.tar.gz#md5=4bdf78ebb7911f215c1972cf71b378f0&egg=simple'
req_url = 'file://' + os.path.join(here, 'packages', tar)
result = run_pip('install', req_url)
egg_folder = env.site_packages / 'simple-1.0-py%s.egg-info' % pyversion
assert egg_folder in result.files_created, str(result)


def test_url_req_with_incorrect_hash_fragment():
"""
Test installing url requirement with incorrect md5 hash fragment fails.
"""
env = reset_env()
tar = 'simple-1.0.tar.gz#md5=123&egg=simple'
req_url = 'file://' + os.path.join(here, 'packages', tar)
result = run_pip('install', req_url, expect_error=True, expect_temp=True)
'Bad md5 hash' in result.stdout, str(result)