2018-11-30 22:59:18 +03:00
|
|
|
#!/usr/bin/env python
|
|
|
|
"""A small script that can act as a trust root for installing pip 8
|
|
|
|
|
|
|
|
Embed this in your project, and your VCS checkout is all you have to trust. In
|
|
|
|
a post-peep era, this lets you claw your way to a hash-checking version of pip,
|
|
|
|
with which you can install the rest of your dependencies safely. All it assumes
|
|
|
|
is Python 2.7 or better and *some* version of pip already installed. If
|
|
|
|
anything goes wrong, it will exit with a non-zero status code.
|
|
|
|
|
|
|
|
"""
|
|
|
|
# This is here so embedded copies are MIT-compliant:
|
|
|
|
# Copyright (c) 2016 Erik Rose
|
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
# of this software and associated documentation files (the "Software"), to
|
|
|
|
# deal in the Software without restriction, including without limitation the
|
|
|
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
# sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
# all copies or substantial portions of the Software.
|
|
|
|
from __future__ import print_function
|
|
|
|
from hashlib import sha256
|
|
|
|
from os.path import join
|
|
|
|
from pipes import quote
|
|
|
|
from shutil import rmtree
|
|
|
|
from subprocess import check_output
|
|
|
|
from sys import exit
|
|
|
|
from tempfile import mkdtemp
|
2020-09-02 04:39:09 +03:00
|
|
|
|
2018-11-30 22:59:18 +03:00
|
|
|
try:
|
|
|
|
from urllib2 import build_opener, HTTPHandler, HTTPSHandler
|
|
|
|
except ImportError:
|
|
|
|
from urllib.request import build_opener, HTTPHandler, HTTPSHandler
|
|
|
|
try:
|
|
|
|
from urlparse import urlparse
|
|
|
|
except ImportError:
|
|
|
|
from urllib.parse import urlparse # 3.4
|
|
|
|
|
|
|
|
|
|
|
|
PACKAGES = [
|
|
|
|
# Pip has no dependencies, as it vendors everything:
|
2020-09-02 04:39:09 +03:00
|
|
|
(
|
|
|
|
"https://pypi.python.org/packages/source/p/pip/pip-8.0.2.tar.gz",
|
|
|
|
"46f4bd0d8dfd51125a554568d646fe4200a3c2c6c36b9f2d06d2212148439521",
|
|
|
|
),
|
2018-11-30 22:59:18 +03:00
|
|
|
# This version of setuptools has only optional dependencies:
|
2020-09-02 04:39:09 +03:00
|
|
|
(
|
|
|
|
"https://pypi.python.org/packages/source/s/setuptools/"
|
|
|
|
"setuptools-19.4.tar.gz",
|
|
|
|
"214bf29933f47cf25e6faa569f710731728a07a19cae91ea64f826051f68a8cf",
|
|
|
|
),
|
2018-11-30 22:59:18 +03:00
|
|
|
# We require Python 2.7 or later because we don't support wheel's
|
|
|
|
# conditional dep on argparse. This version of wheel has no other
|
|
|
|
# dependencies:
|
2020-09-02 04:39:09 +03:00
|
|
|
(
|
|
|
|
"https://pypi.python.org/packages/source/w/wheel/wheel-0.26.0.tar.gz",
|
|
|
|
"eaad353805c180a47545a256e6508835b65a8e830ba1093ed8162f19a50a530c",
|
|
|
|
),
|
2018-11-30 22:59:18 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class HashError(Exception):
|
|
|
|
def __str__(self):
|
|
|
|
url, path, actual, expected = self.args
|
2020-09-02 04:39:09 +03:00
|
|
|
return (
|
|
|
|
"{url} did not match the expected hash {expected}. Instead, "
|
|
|
|
"it was {actual}. The file (left at {path}) may have been "
|
|
|
|
"tampered with.".format(**locals())
|
|
|
|
)
|
2018-11-30 22:59:18 +03:00
|
|
|
|
|
|
|
|
|
|
|
def hashed_download(url, temp, digest):
|
|
|
|
"""Download ``url`` to ``temp``, make sure it has the SHA-256 ``digest``,
|
|
|
|
and return its path."""
|
|
|
|
# Based on pip 1.4.1's URLOpener but with cert verification removed
|
|
|
|
def opener():
|
|
|
|
opener = build_opener(HTTPSHandler())
|
|
|
|
# Strip out HTTPHandler to prevent MITM spoof:
|
|
|
|
for handler in opener.handlers:
|
|
|
|
if isinstance(handler, HTTPHandler):
|
|
|
|
opener.handlers.remove(handler)
|
|
|
|
return opener
|
|
|
|
|
|
|
|
def read_chunks(response, chunk_size):
|
|
|
|
while True:
|
|
|
|
chunk = response.read(chunk_size)
|
|
|
|
if not chunk:
|
|
|
|
break
|
|
|
|
yield chunk
|
|
|
|
|
|
|
|
response = opener().open(url)
|
2020-09-02 04:39:09 +03:00
|
|
|
path = join(temp, urlparse(url).path.split("/")[-1])
|
2018-11-30 22:59:18 +03:00
|
|
|
actual_hash = sha256()
|
2020-09-02 04:39:09 +03:00
|
|
|
with open(path, "wb") as file:
|
2018-11-30 22:59:18 +03:00
|
|
|
for chunk in read_chunks(response, 4096):
|
|
|
|
file.write(chunk)
|
|
|
|
actual_hash.update(chunk)
|
|
|
|
|
|
|
|
actual_digest = actual_hash.hexdigest()
|
|
|
|
if actual_digest != digest:
|
|
|
|
raise HashError(url, path, actual_digest, digest)
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2020-09-02 04:39:09 +03:00
|
|
|
temp = mkdtemp(prefix="pipstrap-")
|
2018-11-30 22:59:18 +03:00
|
|
|
try:
|
2020-09-02 04:39:09 +03:00
|
|
|
downloads = [hashed_download(url, temp, digest) for url, digest in PACKAGES]
|
|
|
|
check_output(
|
|
|
|
"pip install --no-index --no-deps -U "
|
|
|
|
+ " ".join(quote(d) for d in downloads),
|
|
|
|
shell=True,
|
|
|
|
)
|
2018-11-30 22:59:18 +03:00
|
|
|
except HashError as exc:
|
|
|
|
print(exc)
|
|
|
|
except Exception:
|
|
|
|
rmtree(temp)
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
rmtree(temp)
|
|
|
|
return 0
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
2020-09-02 04:39:09 +03:00
|
|
|
if __name__ == "__main__":
|
2018-11-30 22:59:18 +03:00
|
|
|
exit(main())
|