update.py 7.2 KB

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