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