You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

download.py 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. from __future__ import absolute_import
  2. import cgi
  3. import email.utils
  4. import getpass
  5. import json
  6. import logging
  7. import mimetypes
  8. import os
  9. import platform
  10. import re
  11. import shutil
  12. import sys
  13. from pip._vendor import requests, six, urllib3
  14. from pip._vendor.cachecontrol import CacheControlAdapter
  15. from pip._vendor.cachecontrol.caches import FileCache
  16. from pip._vendor.lockfile import LockError
  17. from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
  18. from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
  19. from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
  20. from pip._vendor.requests.structures import CaseInsensitiveDict
  21. from pip._vendor.requests.utils import get_netrc_auth
  22. # NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
  23. # why we ignore the type on this import
  24. from pip._vendor.six.moves import xmlrpc_client # type: ignore
  25. from pip._vendor.six.moves.urllib import parse as urllib_parse
  26. from pip._vendor.six.moves.urllib import request as urllib_request
  27. from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
  28. from pip._vendor.urllib3.util import IS_PYOPENSSL
  29. import pip
  30. from pip._internal.compat import WINDOWS
  31. from pip._internal.exceptions import HashMismatch, InstallationError
  32. from pip._internal.locations import write_delete_marker_file
  33. from pip._internal.models import PyPI
  34. from pip._internal.utils.encoding import auto_decode
  35. from pip._internal.utils.filesystem import check_path_owner
  36. from pip._internal.utils.glibc import libc_ver
  37. from pip._internal.utils.logging import indent_log
  38. from pip._internal.utils.misc import (
  39. ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume,
  40. display_path, format_size, get_installed_version, rmtree, splitext,
  41. unpack_file,
  42. )
  43. from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
  44. from pip._internal.utils.temp_dir import TempDirectory
  45. from pip._internal.utils.ui import DownloadProgressProvider
  46. from pip._internal.vcs import vcs
  47. try:
  48. import ssl # noqa
  49. except ImportError:
  50. ssl = None
  51. HAS_TLS = (ssl is not None) or IS_PYOPENSSL
  52. __all__ = ['get_file_content',
  53. 'is_url', 'url_to_path', 'path_to_url',
  54. 'is_archive_file', 'unpack_vcs_link',
  55. 'unpack_file_url', 'is_vcs_url', 'is_file_url',
  56. 'unpack_http_url', 'unpack_url']
  57. logger = logging.getLogger(__name__)
  58. def user_agent():
  59. """
  60. Return a string representing the user agent.
  61. """
  62. data = {
  63. "installer": {"name": "pip", "version": pip.__version__},
  64. "python": platform.python_version(),
  65. "implementation": {
  66. "name": platform.python_implementation(),
  67. },
  68. }
  69. if data["implementation"]["name"] == 'CPython':
  70. data["implementation"]["version"] = platform.python_version()
  71. elif data["implementation"]["name"] == 'PyPy':
  72. if sys.pypy_version_info.releaselevel == 'final':
  73. pypy_version_info = sys.pypy_version_info[:3]
  74. else:
  75. pypy_version_info = sys.pypy_version_info
  76. data["implementation"]["version"] = ".".join(
  77. [str(x) for x in pypy_version_info]
  78. )
  79. elif data["implementation"]["name"] == 'Jython':
  80. # Complete Guess
  81. data["implementation"]["version"] = platform.python_version()
  82. elif data["implementation"]["name"] == 'IronPython':
  83. # Complete Guess
  84. data["implementation"]["version"] = platform.python_version()
  85. if sys.platform.startswith("linux"):
  86. from pip._vendor import distro
  87. distro_infos = dict(filter(
  88. lambda x: x[1],
  89. zip(["name", "version", "id"], distro.linux_distribution()),
  90. ))
  91. libc = dict(filter(
  92. lambda x: x[1],
  93. zip(["lib", "version"], libc_ver()),
  94. ))
  95. if libc:
  96. distro_infos["libc"] = libc
  97. if distro_infos:
  98. data["distro"] = distro_infos
  99. if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
  100. data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
  101. if platform.system():
  102. data.setdefault("system", {})["name"] = platform.system()
  103. if platform.release():
  104. data.setdefault("system", {})["release"] = platform.release()
  105. if platform.machine():
  106. data["cpu"] = platform.machine()
  107. if HAS_TLS:
  108. data["openssl_version"] = ssl.OPENSSL_VERSION
  109. setuptools_version = get_installed_version("setuptools")
  110. if setuptools_version is not None:
  111. data["setuptools_version"] = setuptools_version
  112. return "{data[installer][name]}/{data[installer][version]} {json}".format(
  113. data=data,
  114. json=json.dumps(data, separators=(",", ":"), sort_keys=True),
  115. )
  116. class MultiDomainBasicAuth(AuthBase):
  117. def __init__(self, prompting=True):
  118. self.prompting = prompting
  119. self.passwords = {}
  120. def __call__(self, req):
  121. parsed = urllib_parse.urlparse(req.url)
  122. # Get the netloc without any embedded credentials
  123. netloc = parsed.netloc.rsplit("@", 1)[-1]
  124. # Set the url of the request to the url without any credentials
  125. req.url = urllib_parse.urlunparse(parsed[:1] + (netloc,) + parsed[2:])
  126. # Use any stored credentials that we have for this netloc
  127. username, password = self.passwords.get(netloc, (None, None))
  128. # Extract credentials embedded in the url if we have none stored
  129. if username is None:
  130. username, password = self.parse_credentials(parsed.netloc)
  131. # Get creds from netrc if we still don't have them
  132. if username is None and password is None:
  133. netrc_auth = get_netrc_auth(req.url)
  134. username, password = netrc_auth if netrc_auth else (None, None)
  135. if username or password:
  136. # Store the username and password
  137. self.passwords[netloc] = (username, password)
  138. # Send the basic auth with this request
  139. req = HTTPBasicAuth(username or "", password or "")(req)
  140. # Attach a hook to handle 401 responses
  141. req.register_hook("response", self.handle_401)
  142. return req
  143. def handle_401(self, resp, **kwargs):
  144. # We only care about 401 responses, anything else we want to just
  145. # pass through the actual response
  146. if resp.status_code != 401:
  147. return resp
  148. # We are not able to prompt the user so simply return the response
  149. if not self.prompting:
  150. return resp
  151. parsed = urllib_parse.urlparse(resp.url)
  152. # Prompt the user for a new username and password
  153. username = six.moves.input("User for %s: " % parsed.netloc)
  154. password = getpass.getpass("Password: ")
  155. # Store the new username and password to use for future requests
  156. if username or password:
  157. self.passwords[parsed.netloc] = (username, password)
  158. # Consume content and release the original connection to allow our new
  159. # request to reuse the same one.
  160. resp.content
  161. resp.raw.release_conn()
  162. # Add our new username and password to the request
  163. req = HTTPBasicAuth(username or "", password or "")(resp.request)
  164. # Send our new request
  165. new_resp = resp.connection.send(req, **kwargs)
  166. new_resp.history.append(resp)
  167. return new_resp
  168. def parse_credentials(self, netloc):
  169. if "@" in netloc:
  170. userinfo = netloc.rsplit("@", 1)[0]
  171. if ":" in userinfo:
  172. user, pwd = userinfo.split(":", 1)
  173. return (urllib_unquote(user), urllib_unquote(pwd))
  174. return urllib_unquote(userinfo), None
  175. return None, None
  176. class LocalFSAdapter(BaseAdapter):
  177. def send(self, request, stream=None, timeout=None, verify=None, cert=None,
  178. proxies=None):
  179. pathname = url_to_path(request.url)
  180. resp = Response()
  181. resp.status_code = 200
  182. resp.url = request.url
  183. try:
  184. stats = os.stat(pathname)
  185. except OSError as exc:
  186. resp.status_code = 404
  187. resp.raw = exc
  188. else:
  189. modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
  190. content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
  191. resp.headers = CaseInsensitiveDict({
  192. "Content-Type": content_type,
  193. "Content-Length": stats.st_size,
  194. "Last-Modified": modified,
  195. })
  196. resp.raw = open(pathname, "rb")
  197. resp.close = resp.raw.close
  198. return resp
  199. def close(self):
  200. pass
  201. class SafeFileCache(FileCache):
  202. """
  203. A file based cache which is safe to use even when the target directory may
  204. not be accessible or writable.
  205. """
  206. def __init__(self, *args, **kwargs):
  207. super(SafeFileCache, self).__init__(*args, **kwargs)
  208. # Check to ensure that the directory containing our cache directory
  209. # is owned by the user current executing pip. If it does not exist
  210. # we will check the parent directory until we find one that does exist.
  211. # If it is not owned by the user executing pip then we will disable
  212. # the cache and log a warning.
  213. if not check_path_owner(self.directory):
  214. logger.warning(
  215. "The directory '%s' or its parent directory is not owned by "
  216. "the current user and the cache has been disabled. Please "
  217. "check the permissions and owner of that directory. If "
  218. "executing pip with sudo, you may want sudo's -H flag.",
  219. self.directory,
  220. )
  221. # Set our directory to None to disable the Cache
  222. self.directory = None
  223. def get(self, *args, **kwargs):
  224. # If we don't have a directory, then the cache should be a no-op.
  225. if self.directory is None:
  226. return
  227. try:
  228. return super(SafeFileCache, self).get(*args, **kwargs)
  229. except (LockError, OSError, IOError):
  230. # We intentionally silence this error, if we can't access the cache
  231. # then we can just skip caching and process the request as if
  232. # caching wasn't enabled.
  233. pass
  234. def set(self, *args, **kwargs):
  235. # If we don't have a directory, then the cache should be a no-op.
  236. if self.directory is None:
  237. return
  238. try:
  239. return super(SafeFileCache, self).set(*args, **kwargs)
  240. except (LockError, OSError, IOError):
  241. # We intentionally silence this error, if we can't access the cache
  242. # then we can just skip caching and process the request as if
  243. # caching wasn't enabled.
  244. pass
  245. def delete(self, *args, **kwargs):
  246. # If we don't have a directory, then the cache should be a no-op.
  247. if self.directory is None:
  248. return
  249. try:
  250. return super(SafeFileCache, self).delete(*args, **kwargs)
  251. except (LockError, OSError, IOError):
  252. # We intentionally silence this error, if we can't access the cache
  253. # then we can just skip caching and process the request as if
  254. # caching wasn't enabled.
  255. pass
  256. class InsecureHTTPAdapter(HTTPAdapter):
  257. def cert_verify(self, conn, url, verify, cert):
  258. conn.cert_reqs = 'CERT_NONE'
  259. conn.ca_certs = None
  260. class PipSession(requests.Session):
  261. timeout = None
  262. def __init__(self, *args, **kwargs):
  263. retries = kwargs.pop("retries", 0)
  264. cache = kwargs.pop("cache", None)
  265. insecure_hosts = kwargs.pop("insecure_hosts", [])
  266. super(PipSession, self).__init__(*args, **kwargs)
  267. # Attach our User Agent to the request
  268. self.headers["User-Agent"] = user_agent()
  269. # Attach our Authentication handler to the session
  270. self.auth = MultiDomainBasicAuth()
  271. # Create our urllib3.Retry instance which will allow us to customize
  272. # how we handle retries.
  273. retries = urllib3.Retry(
  274. # Set the total number of retries that a particular request can
  275. # have.
  276. total=retries,
  277. # A 503 error from PyPI typically means that the Fastly -> Origin
  278. # connection got interrupted in some way. A 503 error in general
  279. # is typically considered a transient error so we'll go ahead and
  280. # retry it.
  281. # A 500 may indicate transient error in Amazon S3
  282. # A 520 or 527 - may indicate transient error in CloudFlare
  283. status_forcelist=[500, 503, 520, 527],
  284. # Add a small amount of back off between failed requests in
  285. # order to prevent hammering the service.
  286. backoff_factor=0.25,
  287. )
  288. # We want to _only_ cache responses on securely fetched origins. We do
  289. # this because we can't validate the response of an insecurely fetched
  290. # origin, and we don't want someone to be able to poison the cache and
  291. # require manual eviction from the cache to fix it.
  292. if cache:
  293. secure_adapter = CacheControlAdapter(
  294. cache=SafeFileCache(cache, use_dir_lock=True),
  295. max_retries=retries,
  296. )
  297. else:
  298. secure_adapter = HTTPAdapter(max_retries=retries)
  299. # Our Insecure HTTPAdapter disables HTTPS validation. It does not
  300. # support caching (see above) so we'll use it for all http:// URLs as
  301. # well as any https:// host that we've marked as ignoring TLS errors
  302. # for.
  303. insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
  304. self.mount("https://", secure_adapter)
  305. self.mount("http://", insecure_adapter)
  306. # Enable file:// urls
  307. self.mount("file://", LocalFSAdapter())
  308. # We want to use a non-validating adapter for any requests which are
  309. # deemed insecure.
  310. for host in insecure_hosts:
  311. self.mount("https://{}/".format(host), insecure_adapter)
  312. def request(self, method, url, *args, **kwargs):
  313. # Allow setting a default timeout on a session
  314. kwargs.setdefault("timeout", self.timeout)
  315. # Dispatch the actual request
  316. return super(PipSession, self).request(method, url, *args, **kwargs)
  317. def get_file_content(url, comes_from=None, session=None):
  318. """Gets the content of a file; it may be a filename, file: URL, or
  319. http: URL. Returns (location, content). Content is unicode.
  320. :param url: File path or url.
  321. :param comes_from: Origin description of requirements.
  322. :param session: Instance of pip.download.PipSession.
  323. """
  324. if session is None:
  325. raise TypeError(
  326. "get_file_content() missing 1 required keyword argument: 'session'"
  327. )
  328. match = _scheme_re.search(url)
  329. if match:
  330. scheme = match.group(1).lower()
  331. if (scheme == 'file' and comes_from and
  332. comes_from.startswith('http')):
  333. raise InstallationError(
  334. 'Requirements file %s references URL %s, which is local'
  335. % (comes_from, url))
  336. if scheme == 'file':
  337. path = url.split(':', 1)[1]
  338. path = path.replace('\\', '/')
  339. match = _url_slash_drive_re.match(path)
  340. if match:
  341. path = match.group(1) + ':' + path.split('|', 1)[1]
  342. path = urllib_parse.unquote(path)
  343. if path.startswith('/'):
  344. path = '/' + path.lstrip('/')
  345. url = path
  346. else:
  347. # FIXME: catch some errors
  348. resp = session.get(url)
  349. resp.raise_for_status()
  350. return resp.url, resp.text
  351. try:
  352. with open(url, 'rb') as f:
  353. content = auto_decode(f.read())
  354. except IOError as exc:
  355. raise InstallationError(
  356. 'Could not open requirements file: %s' % str(exc)
  357. )
  358. return url, content
  359. _scheme_re = re.compile(r'^(http|https|file):', re.I)
  360. _url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
  361. def is_url(name):
  362. """Returns true if the name looks like a URL"""
  363. if ':' not in name:
  364. return False
  365. scheme = name.split(':', 1)[0].lower()
  366. return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes
  367. def url_to_path(url):
  368. """
  369. Convert a file: URL to a path.
  370. """
  371. assert url.startswith('file:'), (
  372. "You can only turn file: urls into filenames (not %r)" % url)
  373. _, netloc, path, _, _ = urllib_parse.urlsplit(url)
  374. # if we have a UNC path, prepend UNC share notation
  375. if netloc:
  376. netloc = '\\\\' + netloc
  377. path = urllib_request.url2pathname(netloc + path)
  378. return path
  379. def path_to_url(path):
  380. """
  381. Convert a path to a file: URL. The path will be made absolute and have
  382. quoted path parts.
  383. """
  384. path = os.path.normpath(os.path.abspath(path))
  385. url = urllib_parse.urljoin('file:', urllib_request.pathname2url(path))
  386. return url
  387. def is_archive_file(name):
  388. """Return True if `name` is a considered as an archive file."""
  389. ext = splitext(name)[1].lower()
  390. if ext in ARCHIVE_EXTENSIONS:
  391. return True
  392. return False
  393. def unpack_vcs_link(link, location):
  394. vcs_backend = _get_used_vcs_backend(link)
  395. vcs_backend.unpack(location)
  396. def _get_used_vcs_backend(link):
  397. for backend in vcs.backends:
  398. if link.scheme in backend.schemes:
  399. vcs_backend = backend(link.url)
  400. return vcs_backend
  401. def is_vcs_url(link):
  402. return bool(_get_used_vcs_backend(link))
  403. def is_file_url(link):
  404. return link.url.lower().startswith('file:')
  405. def is_dir_url(link):
  406. """Return whether a file:// Link points to a directory.
  407. ``link`` must not have any other scheme but file://. Call is_file_url()
  408. first.
  409. """
  410. link_path = url_to_path(link.url_without_fragment)
  411. return os.path.isdir(link_path)
  412. def _progress_indicator(iterable, *args, **kwargs):
  413. return iterable
  414. def _download_url(resp, link, content_file, hashes, progress_bar):
  415. try:
  416. total_length = int(resp.headers['content-length'])
  417. except (ValueError, KeyError, TypeError):
  418. total_length = 0
  419. cached_resp = getattr(resp, "from_cache", False)
  420. if logger.getEffectiveLevel() > logging.INFO:
  421. show_progress = False
  422. elif cached_resp:
  423. show_progress = False
  424. elif total_length > (40 * 1000):
  425. show_progress = True
  426. elif not total_length:
  427. show_progress = True
  428. else:
  429. show_progress = False
  430. show_url = link.show_url
  431. def resp_read(chunk_size):
  432. try:
  433. # Special case for urllib3.
  434. for chunk in resp.raw.stream(
  435. chunk_size,
  436. # We use decode_content=False here because we don't
  437. # want urllib3 to mess with the raw bytes we get
  438. # from the server. If we decompress inside of
  439. # urllib3 then we cannot verify the checksum
  440. # because the checksum will be of the compressed
  441. # file. This breakage will only occur if the
  442. # server adds a Content-Encoding header, which
  443. # depends on how the server was configured:
  444. # - Some servers will notice that the file isn't a
  445. # compressible file and will leave the file alone
  446. # and with an empty Content-Encoding
  447. # - Some servers will notice that the file is
  448. # already compressed and will leave the file
  449. # alone and will add a Content-Encoding: gzip
  450. # header
  451. # - Some servers won't notice anything at all and
  452. # will take a file that's already been compressed
  453. # and compress it again and set the
  454. # Content-Encoding: gzip header
  455. #
  456. # By setting this not to decode automatically we
  457. # hope to eliminate problems with the second case.
  458. decode_content=False):
  459. yield chunk
  460. except AttributeError:
  461. # Standard file-like object.
  462. while True:
  463. chunk = resp.raw.read(chunk_size)
  464. if not chunk:
  465. break
  466. yield chunk
  467. def written_chunks(chunks):
  468. for chunk in chunks:
  469. content_file.write(chunk)
  470. yield chunk
  471. progress_indicator = _progress_indicator
  472. if link.netloc == PyPI.netloc:
  473. url = show_url
  474. else:
  475. url = link.url_without_fragment
  476. if show_progress: # We don't show progress on cached responses
  477. progress_indicator = DownloadProgressProvider(progress_bar,
  478. max=total_length)
  479. if total_length:
  480. logger.info("Downloading %s (%s)", url, format_size(total_length))
  481. else:
  482. logger.info("Downloading %s", url)
  483. elif cached_resp:
  484. logger.info("Using cached %s", url)
  485. else:
  486. logger.info("Downloading %s", url)
  487. logger.debug('Downloading from URL %s', link)
  488. downloaded_chunks = written_chunks(
  489. progress_indicator(
  490. resp_read(CONTENT_CHUNK_SIZE),
  491. CONTENT_CHUNK_SIZE
  492. )
  493. )
  494. if hashes:
  495. hashes.check_against_chunks(downloaded_chunks)
  496. else:
  497. consume(downloaded_chunks)
  498. def _copy_file(filename, location, link):
  499. copy = True
  500. download_location = os.path.join(location, link.filename)
  501. if os.path.exists(download_location):
  502. response = ask_path_exists(
  503. 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)abort' %
  504. display_path(download_location), ('i', 'w', 'b', 'a'))
  505. if response == 'i':
  506. copy = False
  507. elif response == 'w':
  508. logger.warning('Deleting %s', display_path(download_location))
  509. os.remove(download_location)
  510. elif response == 'b':
  511. dest_file = backup_dir(download_location)
  512. logger.warning(
  513. 'Backing up %s to %s',
  514. display_path(download_location),
  515. display_path(dest_file),
  516. )
  517. shutil.move(download_location, dest_file)
  518. elif response == 'a':
  519. sys.exit(-1)
  520. if copy:
  521. shutil.copy(filename, download_location)
  522. logger.info('Saved %s', display_path(download_location))
  523. def unpack_http_url(link, location, download_dir=None,
  524. session=None, hashes=None, progress_bar="on"):
  525. if session is None:
  526. raise TypeError(
  527. "unpack_http_url() missing 1 required keyword argument: 'session'"
  528. )
  529. with TempDirectory(kind="unpack") as temp_dir:
  530. # If a download dir is specified, is the file already downloaded there?
  531. already_downloaded_path = None
  532. if download_dir:
  533. already_downloaded_path = _check_download_dir(link,
  534. download_dir,
  535. hashes)
  536. if already_downloaded_path:
  537. from_path = already_downloaded_path
  538. content_type = mimetypes.guess_type(from_path)[0]
  539. else:
  540. # let's download to a tmp dir
  541. from_path, content_type = _download_http_url(link,
  542. session,
  543. temp_dir.path,
  544. hashes,
  545. progress_bar)
  546. # unpack the archive to the build dir location. even when only
  547. # downloading archives, they have to be unpacked to parse dependencies
  548. unpack_file(from_path, location, content_type, link)
  549. # a download dir is specified; let's copy the archive there
  550. if download_dir and not already_downloaded_path:
  551. _copy_file(from_path, download_dir, link)
  552. if not already_downloaded_path:
  553. os.unlink(from_path)
  554. def unpack_file_url(link, location, download_dir=None, hashes=None):
  555. """Unpack link into location.
  556. If download_dir is provided and link points to a file, make a copy
  557. of the link file inside download_dir.
  558. """
  559. link_path = url_to_path(link.url_without_fragment)
  560. # If it's a url to a local directory
  561. if is_dir_url(link):
  562. if os.path.isdir(location):
  563. rmtree(location)
  564. shutil.copytree(link_path, location, symlinks=True)
  565. if download_dir:
  566. logger.info('Link is a directory, ignoring download_dir')
  567. return
  568. # If --require-hashes is off, `hashes` is either empty, the
  569. # link's embedded hash, or MissingHashes; it is required to
  570. # match. If --require-hashes is on, we are satisfied by any
  571. # hash in `hashes` matching: a URL-based or an option-based
  572. # one; no internet-sourced hash will be in `hashes`.
  573. if hashes:
  574. hashes.check_against_path(link_path)
  575. # If a download dir is specified, is the file already there and valid?
  576. already_downloaded_path = None
  577. if download_dir:
  578. already_downloaded_path = _check_download_dir(link,
  579. download_dir,
  580. hashes)
  581. if already_downloaded_path:
  582. from_path = already_downloaded_path
  583. else:
  584. from_path = link_path
  585. content_type = mimetypes.guess_type(from_path)[0]
  586. # unpack the archive to the build dir location. even when only downloading
  587. # archives, they have to be unpacked to parse dependencies
  588. unpack_file(from_path, location, content_type, link)
  589. # a download dir is specified and not already downloaded
  590. if download_dir and not already_downloaded_path:
  591. _copy_file(from_path, download_dir, link)
  592. def _copy_dist_from_dir(link_path, location):
  593. """Copy distribution files in `link_path` to `location`.
  594. Invoked when user requests to install a local directory. E.g.:
  595. pip install .
  596. pip install ~/dev/git-repos/python-prompt-toolkit
  597. """
  598. # Note: This is currently VERY SLOW if you have a lot of data in the
  599. # directory, because it copies everything with `shutil.copytree`.
  600. # What it should really do is build an sdist and install that.
  601. # See https://github.com/pypa/pip/issues/2195
  602. if os.path.isdir(location):
  603. rmtree(location)
  604. # build an sdist
  605. setup_py = 'setup.py'
  606. sdist_args = [sys.executable]
  607. sdist_args.append('-c')
  608. sdist_args.append(SETUPTOOLS_SHIM % setup_py)
  609. sdist_args.append('sdist')
  610. sdist_args += ['--dist-dir', location]
  611. logger.info('Running setup.py sdist for %s', link_path)
  612. with indent_log():
  613. call_subprocess(sdist_args, cwd=link_path, show_stdout=False)
  614. # unpack sdist into `location`
  615. sdist = os.path.join(location, os.listdir(location)[0])
  616. logger.info('Unpacking sdist %s into %s', sdist, location)
  617. unpack_file(sdist, location, content_type=None, link=None)
  618. class PipXmlrpcTransport(xmlrpc_client.Transport):
  619. """Provide a `xmlrpclib.Transport` implementation via a `PipSession`
  620. object.
  621. """
  622. def __init__(self, index_url, session, use_datetime=False):
  623. xmlrpc_client.Transport.__init__(self, use_datetime)
  624. index_parts = urllib_parse.urlparse(index_url)
  625. self._scheme = index_parts.scheme
  626. self._session = session
  627. def request(self, host, handler, request_body, verbose=False):
  628. parts = (self._scheme, host, handler, None, None, None)
  629. url = urllib_parse.urlunparse(parts)
  630. try:
  631. headers = {'Content-Type': 'text/xml'}
  632. response = self._session.post(url, data=request_body,
  633. headers=headers, stream=True)
  634. response.raise_for_status()
  635. self.verbose = verbose
  636. return self.parse_response(response.raw)
  637. except requests.HTTPError as exc:
  638. logger.critical(
  639. "HTTP error %s while getting %s",
  640. exc.response.status_code, url,
  641. )
  642. raise
  643. def unpack_url(link, location, download_dir=None,
  644. only_download=False, session=None, hashes=None,
  645. progress_bar="on"):
  646. """Unpack link.
  647. If link is a VCS link:
  648. if only_download, export into download_dir and ignore location
  649. else unpack into location
  650. for other types of link:
  651. - unpack into location
  652. - if download_dir, copy the file into download_dir
  653. - if only_download, mark location for deletion
  654. :param hashes: A Hashes object, one of whose embedded hashes must match,
  655. or HashMismatch will be raised. If the Hashes is empty, no matches are
  656. required, and unhashable types of requirements (like VCS ones, which
  657. would ordinarily raise HashUnsupported) are allowed.
  658. """
  659. # non-editable vcs urls
  660. if is_vcs_url(link):
  661. unpack_vcs_link(link, location)
  662. # file urls
  663. elif is_file_url(link):
  664. unpack_file_url(link, location, download_dir, hashes=hashes)
  665. # http urls
  666. else:
  667. if session is None:
  668. session = PipSession()
  669. unpack_http_url(
  670. link,
  671. location,
  672. download_dir,
  673. session,
  674. hashes=hashes,
  675. progress_bar=progress_bar
  676. )
  677. if only_download:
  678. write_delete_marker_file(location)
  679. def _download_http_url(link, session, temp_dir, hashes, progress_bar):
  680. """Download link url into temp_dir using provided session"""
  681. target_url = link.url.split('#', 1)[0]
  682. try:
  683. resp = session.get(
  684. target_url,
  685. # We use Accept-Encoding: identity here because requests
  686. # defaults to accepting compressed responses. This breaks in
  687. # a variety of ways depending on how the server is configured.
  688. # - Some servers will notice that the file isn't a compressible
  689. # file and will leave the file alone and with an empty
  690. # Content-Encoding
  691. # - Some servers will notice that the file is already
  692. # compressed and will leave the file alone and will add a
  693. # Content-Encoding: gzip header
  694. # - Some servers won't notice anything at all and will take
  695. # a file that's already been compressed and compress it again
  696. # and set the Content-Encoding: gzip header
  697. # By setting this to request only the identity encoding We're
  698. # hoping to eliminate the third case. Hopefully there does not
  699. # exist a server which when given a file will notice it is
  700. # already compressed and that you're not asking for a
  701. # compressed file and will then decompress it before sending
  702. # because if that's the case I don't think it'll ever be
  703. # possible to make this work.
  704. headers={"Accept-Encoding": "identity"},
  705. stream=True,
  706. )
  707. resp.raise_for_status()
  708. except requests.HTTPError as exc:
  709. logger.critical(
  710. "HTTP error %s while getting %s", exc.response.status_code, link,
  711. )
  712. raise
  713. content_type = resp.headers.get('content-type', '')
  714. filename = link.filename # fallback
  715. # Have a look at the Content-Disposition header for a better guess
  716. content_disposition = resp.headers.get('content-disposition')
  717. if content_disposition:
  718. type, params = cgi.parse_header(content_disposition)
  719. # We use ``or`` here because we don't want to use an "empty" value
  720. # from the filename param.
  721. filename = params.get('filename') or filename
  722. ext = splitext(filename)[1]
  723. if not ext:
  724. ext = mimetypes.guess_extension(content_type)
  725. if ext:
  726. filename += ext
  727. if not ext and link.url != resp.url:
  728. ext = os.path.splitext(resp.url)[1]
  729. if ext:
  730. filename += ext
  731. file_path = os.path.join(temp_dir, filename)
  732. with open(file_path, 'wb') as content_file:
  733. _download_url(resp, link, content_file, hashes, progress_bar)
  734. return file_path, content_type
  735. def _check_download_dir(link, download_dir, hashes):
  736. """ Check download_dir for previously downloaded file with correct hash
  737. If a correct file is found return its path else None
  738. """
  739. download_path = os.path.join(download_dir, link.filename)
  740. if os.path.exists(download_path):
  741. # If already downloaded, does its hash match?
  742. logger.info('File was already downloaded %s', download_path)
  743. if hashes:
  744. try:
  745. hashes.check_against_path(download_path)
  746. except HashMismatch:
  747. logger.warning(
  748. 'Previously-downloaded file %s has bad hash. '
  749. 'Re-downloading.',
  750. download_path
  751. )
  752. os.unlink(download_path)
  753. return None
  754. return download_path
  755. return None