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.

192 lines
7.0 KiB

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
12 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
12 years ago
10 years ago
12 years ago
10 years ago
12 years ago
  1. from __future__ import unicode_literals
  2. import io
  3. import json
  4. import traceback
  5. import hashlib
  6. import os
  7. import subprocess
  8. import sys
  9. from zipimport import zipimporter
  10. from .utils import encode_compat_str
  11. from .version import __version__
  12. def rsa_verify(message, signature, key):
  13. from hashlib import sha256
  14. assert isinstance(message, bytes)
  15. byte_size = (len(bin(key[0])) - 2 + 8 - 1) // 8
  16. signature = ('%x' % pow(int(signature, 16), key[1], key[0])).encode()
  17. signature = (byte_size * 2 - len(signature)) * b'0' + signature
  18. asn1 = b'3031300d060960864801650304020105000420'
  19. asn1 += sha256(message).hexdigest().encode()
  20. if byte_size < len(asn1) // 2 + 11:
  21. return False
  22. expected = b'0001' + (byte_size - len(asn1) // 2 - 3) * b'ff' + b'00' + asn1
  23. return expected == signature
  24. def update_self(to_screen, verbose, opener, prefer_insecure=False):
  25. """Update the program file with the latest version from the repository"""
  26. UPDATE_URL = '//rg3.github.io/youtube-dl/update/'
  27. VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
  28. JSON_URL = UPDATE_URL + 'versions.json'
  29. UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
  30. if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
  31. to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')
  32. return
  33. def guess_scheme(url, insecure=False):
  34. return 'http%s:%s' % ('' if insecure is True else 's', url)
  35. # Check if there is a new version
  36. try:
  37. newversion = opener.open(guess_scheme(
  38. VERSION_URL, prefer_insecure)).read().decode('utf-8').strip()
  39. except Exception:
  40. if verbose:
  41. to_screen(encode_compat_str(traceback.format_exc()))
  42. to_screen('ERROR: can\'t find the current version. Please try again later.')
  43. return
  44. if newversion == __version__:
  45. to_screen('youtube-dl is up-to-date (' + __version__ + ')')
  46. return
  47. # Download and check versions info
  48. try:
  49. versions_info = opener.open(guess_scheme(
  50. JSON_URL, prefer_insecure)).read().decode('utf-8')
  51. versions_info = json.loads(versions_info)
  52. except Exception:
  53. if verbose:
  54. to_screen(encode_compat_str(traceback.format_exc()))
  55. to_screen('ERROR: can\'t obtain versions info. Please try again later.')
  56. return
  57. if 'signature' not in versions_info:
  58. to_screen('ERROR: the versions file is not signed or corrupted. Aborting.')
  59. return
  60. signature = versions_info['signature']
  61. del versions_info['signature']
  62. if not rsa_verify(json.dumps(versions_info, sort_keys=True).encode('utf-8'), signature, UPDATES_RSA_KEY):
  63. to_screen('ERROR: the versions file signature is invalid. Aborting.')
  64. return
  65. version_id = versions_info['latest']
  66. def version_tuple(version_str):
  67. return tuple(map(int, version_str.split('.')))
  68. if version_tuple(__version__) >= version_tuple(version_id):
  69. to_screen('youtube-dl is up to date (%s)' % __version__)
  70. return
  71. to_screen('Updating to version ' + version_id + ' ...')
  72. version = versions_info['versions'][version_id]
  73. print_notes(to_screen, versions_info['versions'])
  74. # sys.executable is set to the full pathname of the exe-file for py2exe
  75. filename = sys.executable if hasattr(sys, 'frozen') else sys.argv[0]
  76. if not os.access(filename, os.W_OK):
  77. to_screen('ERROR: no write permissions on %s' % filename)
  78. return
  79. # Py2EXE
  80. if hasattr(sys, 'frozen'):
  81. exe = filename
  82. directory = os.path.dirname(exe)
  83. if not os.access(directory, os.W_OK):
  84. to_screen('ERROR: no write permissions on %s' % directory)
  85. return
  86. try:
  87. urlh = opener.open(version['exe'][0])
  88. newcontent = urlh.read()
  89. urlh.close()
  90. except (IOError, OSError):
  91. if verbose:
  92. to_screen(encode_compat_str(traceback.format_exc()))
  93. to_screen('ERROR: unable to download latest version')
  94. return
  95. newcontent_hash = hashlib.sha256(newcontent).hexdigest()
  96. if newcontent_hash != version['exe'][1]:
  97. to_screen('ERROR: the downloaded file hash does not match. Aborting.')
  98. return
  99. try:
  100. with open(exe + '.new', 'wb') as outf:
  101. outf.write(newcontent)
  102. except (IOError, OSError):
  103. if verbose:
  104. to_screen(encode_compat_str(traceback.format_exc()))
  105. to_screen('ERROR: unable to write the new version')
  106. return
  107. try:
  108. bat = os.path.join(directory, 'youtube-dl-updater.bat')
  109. with io.open(bat, 'w') as batfile:
  110. batfile.write('''
  111. @echo off
  112. echo Waiting for file handle to be closed ...
  113. ping 127.0.0.1 -n 5 -w 1000 > NUL
  114. move /Y "%s.new" "%s" > NUL
  115. echo Updated youtube-dl to version %s.
  116. start /b "" cmd /c del "%%~f0"&exit /b"
  117. \n''' % (exe, exe, version_id))
  118. subprocess.Popen([bat]) # Continues to run in the background
  119. return # Do not show premature success messages
  120. except (IOError, OSError):
  121. if verbose:
  122. to_screen(encode_compat_str(traceback.format_exc()))
  123. to_screen('ERROR: unable to overwrite current version')
  124. return
  125. # Zip unix package
  126. elif isinstance(globals().get('__loader__'), zipimporter):
  127. try:
  128. urlh = opener.open(version['bin'][0])
  129. newcontent = urlh.read()
  130. urlh.close()
  131. except (IOError, OSError):
  132. if verbose:
  133. to_screen(encode_compat_str(traceback.format_exc()))
  134. to_screen('ERROR: unable to download latest version')
  135. return
  136. newcontent_hash = hashlib.sha256(newcontent).hexdigest()
  137. if newcontent_hash != version['bin'][1]:
  138. to_screen('ERROR: the downloaded file hash does not match. Aborting.')
  139. return
  140. try:
  141. with open(filename, 'wb') as outf:
  142. outf.write(newcontent)
  143. except (IOError, OSError):
  144. if verbose:
  145. to_screen(encode_compat_str(traceback.format_exc()))
  146. to_screen('ERROR: unable to overwrite current version')
  147. return
  148. to_screen('Updated youtube-dl. Restart youtube-dl to use the new version.')
  149. def get_notes(versions, fromVersion):
  150. notes = []
  151. for v, vdata in sorted(versions.items()):
  152. if v > fromVersion:
  153. notes.extend(vdata.get('notes', []))
  154. return notes
  155. def print_notes(to_screen, versions, fromVersion=__version__):
  156. notes = get_notes(versions, fromVersion)
  157. if notes:
  158. to_screen('PLEASE NOTE:')
  159. for note in notes:
  160. to_screen(note)