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.

212 lines
7.3 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 .utils import make_HTTPS_handler
  15. from .version import __version__
  16. def rsa_verify(message, signature, key):
  17. from struct import pack
  18. from hashlib import sha256
  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. https_handler = make_HTTPS_handler({})
  53. opener = compat_urllib_request.build_opener(https_handler)
  54. # Check if there is a new version
  55. try:
  56. newversion = opener.open(VERSION_URL).read().decode('utf-8').strip()
  57. except Exception:
  58. if verbose:
  59. to_screen(compat_str(traceback.format_exc()))
  60. to_screen('ERROR: can\'t find the current version. Please try again later.')
  61. return
  62. if newversion == __version__:
  63. to_screen('youtube-dl is up-to-date (' + __version__ + ')')
  64. return
  65. # Download and check versions info
  66. try:
  67. versions_info = opener.open(JSON_URL).read().decode('utf-8')
  68. versions_info = json.loads(versions_info)
  69. except Exception:
  70. if verbose:
  71. to_screen(compat_str(traceback.format_exc()))
  72. to_screen('ERROR: can\'t obtain versions info. Please try again later.')
  73. return
  74. if 'signature' not in versions_info:
  75. to_screen('ERROR: the versions file is not signed or corrupted. Aborting.')
  76. return
  77. signature = versions_info['signature']
  78. del versions_info['signature']
  79. if not rsa_verify(json.dumps(versions_info, sort_keys=True).encode('utf-8'), signature, UPDATES_RSA_KEY):
  80. to_screen('ERROR: the versions file signature is invalid. Aborting.')
  81. return
  82. version_id = versions_info['latest']
  83. def version_tuple(version_str):
  84. return tuple(map(int, version_str.split('.')))
  85. if version_tuple(__version__) >= version_tuple(version_id):
  86. to_screen('youtube-dl is up to date (%s)' % __version__)
  87. return
  88. to_screen('Updating to version ' + version_id + ' ...')
  89. version = versions_info['versions'][version_id]
  90. print_notes(to_screen, versions_info['versions'])
  91. filename = sys.argv[0]
  92. # Py2EXE: Filename could be different
  93. if hasattr(sys, "frozen") and not os.path.isfile(filename):
  94. if os.path.isfile(filename + '.exe'):
  95. filename += '.exe'
  96. if not os.access(filename, os.W_OK):
  97. to_screen('ERROR: no write permissions on %s' % filename)
  98. return
  99. # Py2EXE
  100. if hasattr(sys, "frozen"):
  101. exe = os.path.abspath(filename)
  102. directory = os.path.dirname(exe)
  103. if not os.access(directory, os.W_OK):
  104. to_screen('ERROR: no write permissions on %s' % directory)
  105. return
  106. try:
  107. urlh = opener.open(version['exe'][0])
  108. newcontent = urlh.read()
  109. urlh.close()
  110. except (IOError, OSError):
  111. if verbose:
  112. to_screen(compat_str(traceback.format_exc()))
  113. to_screen('ERROR: unable to download latest version')
  114. return
  115. newcontent_hash = hashlib.sha256(newcontent).hexdigest()
  116. if newcontent_hash != version['exe'][1]:
  117. to_screen('ERROR: the downloaded file hash does not match. Aborting.')
  118. return
  119. try:
  120. with open(exe + '.new', 'wb') as outf:
  121. outf.write(newcontent)
  122. except (IOError, OSError):
  123. if verbose:
  124. to_screen(compat_str(traceback.format_exc()))
  125. to_screen('ERROR: unable to write the new version')
  126. return
  127. try:
  128. bat = os.path.join(directory, 'youtube-dl-updater.bat')
  129. with io.open(bat, 'w') as batfile:
  130. batfile.write('''
  131. @echo off
  132. echo Waiting for file handle to be closed ...
  133. ping 127.0.0.1 -n 5 -w 1000 > NUL
  134. move /Y "%s.new" "%s" > NUL
  135. echo Updated youtube-dl to version %s.
  136. start /b "" cmd /c del "%%~f0"&exit /b"
  137. \n''' % (exe, exe, version_id))
  138. subprocess.Popen([bat]) # Continues to run in the background
  139. return # Do not show premature success messages
  140. except (IOError, OSError):
  141. if verbose:
  142. to_screen(compat_str(traceback.format_exc()))
  143. to_screen('ERROR: unable to overwrite current version')
  144. return
  145. # Zip unix package
  146. elif isinstance(globals().get('__loader__'), zipimporter):
  147. try:
  148. urlh = opener.open(version['bin'][0])
  149. newcontent = urlh.read()
  150. urlh.close()
  151. except (IOError, OSError):
  152. if verbose:
  153. to_screen(compat_str(traceback.format_exc()))
  154. to_screen('ERROR: unable to download latest version')
  155. return
  156. newcontent_hash = hashlib.sha256(newcontent).hexdigest()
  157. if newcontent_hash != version['bin'][1]:
  158. to_screen('ERROR: the downloaded file hash does not match. Aborting.')
  159. return
  160. try:
  161. with open(filename, 'wb') as outf:
  162. outf.write(newcontent)
  163. except (IOError, OSError):
  164. if verbose:
  165. to_screen(compat_str(traceback.format_exc()))
  166. to_screen('ERROR: unable to overwrite current version')
  167. return
  168. to_screen('Updated youtube-dl. Restart youtube-dl to use the new version.')
  169. def get_notes(versions, fromVersion):
  170. notes = []
  171. for v, vdata in sorted(versions.items()):
  172. if v > fromVersion:
  173. notes.extend(vdata.get('notes', []))
  174. return notes
  175. def print_notes(to_screen, versions, fromVersion=__version__):
  176. notes = get_notes(versions, fromVersion)
  177. if notes:
  178. to_screen('PLEASE NOTE:')
  179. for note in notes:
  180. to_screen(note)