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.

208 lines
7.2 KiB

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