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