]> jfr.im git - yt-dlp.git/blame - yt_dlp/update.py
Handle Basic Auth `user:pass` in URLs
[yt-dlp.git] / yt_dlp / 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
e5813e53 8import platform
d2790370 9import subprocess
46353f67 10import sys
d5ed35b6
FV
11from zipimport import zipimporter
12
bfe2b8cf 13from .compat import compat_realpath
c0384f22 14from .utils import encode_compat_str
d3d3e2e3 15
d5ed35b6
FV
16from .version import __version__
17
5f6a1245 18
3dd264bf 19''' # Not signed
d5ed35b6 20def rsa_verify(message, signature, key):
d5ed35b6 21 from hashlib import sha256
15938ab6 22 assert isinstance(message, bytes)
4d318be1
FV
23 byte_size = (len(bin(key[0])) - 2 + 8 - 1) // 8
24 signature = ('%x' % pow(int(signature, 16), key[1], key[0])).encode()
25 signature = (byte_size * 2 - len(signature)) * b'0' + signature
26 asn1 = b'3031300d060960864801650304020105000420'
27 asn1 += sha256(message).hexdigest().encode()
28 if byte_size < len(asn1) // 2 + 11:
5f6a1245 29 return False
4d318be1
FV
30 expected = b'0001' + (byte_size - len(asn1) // 2 - 3) * b'ff' + b'00' + asn1
31 return expected == signature
3dd264bf 32'''
d5ed35b6 33
d7386f62 34
0c3e5f49 35def update_self(to_screen, verbose, opener):
e5813e53 36 """
37 Update the program file with the latest version from the repository
38 Returns whether the program should terminate
39 """
d5ed35b6 40
7a5c1cfe 41 JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
d5ed35b6 42
44f705d0 43 def calc_sha256sum(path):
fa57af1e
U
44 h = hashlib.sha256()
45 b = bytearray(128 * 1024)
46 mv = memoryview(b)
44f705d0 47 with open(os.path.realpath(path), 'rb', buffering=0) as f:
fa57af1e
U
48 for n in iter(lambda: f.readinto(mv), 0):
49 h.update(mv[:n])
50 return h.hexdigest()
51
611c1dd9 52 if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
e28f1c0a 53 to_screen('It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. Please use that to update')
d5ed35b6
FV
54 return
55
7815e555 56 # sys.executable is set to the full pathname of the exe-file for py2exe
57 # though symlinks are not followed so that we need to do this manually
58 # with help of realpath
59 filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0])
60 to_screen('Current Build Hash %s' % calc_sha256sum(filename))
61
d5ed35b6
FV
62 # Download and check versions info
63 try:
3dd264bf 64 version_info = opener.open(JSON_URL).read().decode('utf-8')
65 version_info = json.loads(version_info)
70a1165b 66 except Exception:
5f6a1245 67 if verbose:
c0384f22 68 to_screen(encode_compat_str(traceback.format_exc()))
e28f1c0a 69 to_screen('ERROR: can\'t obtain versions info. Please try again later')
7a5c1cfe 70 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
d5ed35b6
FV
71 return
72
d7386f62
PH
73 def version_tuple(version_str):
74 return tuple(map(int, version_str.split('.')))
3dd264bf 75
e5813e53 76 version_id = version_info['tag_name']
2e767313 77 if version_tuple(__version__) >= version_tuple(version_id):
e5813e53 78 to_screen('yt-dlp is up to date (%s)' % __version__)
d7386f62
PH
79 return
80
15938ab6 81 to_screen('Updating to version ' + version_id + ' ...')
3bf79c75 82
44f705d0 83 version_labels = {
84 'zip_3': '',
85 'zip_2': '',
86 # 'zip_2': '_py2',
87 'exe_64': '.exe',
88 'exe_32': '_x86.exe',
89 }
90
e5813e53 91 def get_bin_info(bin_or_exe, version):
44f705d0 92 label = version_labels['%s_%s' % (bin_or_exe, version)]
93 return next(
7a5c1cfe
P
94 (i for i in version_info['assets'] if i['name'] == 'yt-dlp%s' % label),
95 {})
44f705d0 96
97 def get_sha256sum(bin_or_exe, version):
98 label = version_labels['%s_%s' % (bin_or_exe, version)]
99 urlh = next(
100 (i for i in version_info['assets']
101 if i['name'] in ('SHA2-256SUMS')), {}).get('browser_download_url')
102 if not urlh:
103 return None
104 hash_data = opener.open(urlh).read().decode('utf-8')
105 hashes = list(map(lambda x: x.split(':'), hash_data.splitlines()))
e5813e53 106 return next(
7a5c1cfe
P
107 (i[1] for i in hashes if i[0] == 'yt-dlp%s' % label),
108 None)
d5ed35b6
FV
109
110 if not os.access(filename, os.W_OK):
15938ab6 111 to_screen('ERROR: no write permissions on %s' % filename)
d5ed35b6
FV
112 return
113
3dd264bf 114 # PyInstaller
611c1dd9 115 if hasattr(sys, 'frozen'):
e9297256 116 exe = filename
d5ed35b6
FV
117 directory = os.path.dirname(exe)
118 if not os.access(directory, os.W_OK):
15938ab6 119 to_screen('ERROR: no write permissions on %s' % directory)
d5ed35b6
FV
120 return
121
122 try:
e5813e53 123 arch = platform.architecture()[0][:2]
44f705d0 124 url = get_bin_info('exe', arch).get('browser_download_url')
125 if not url:
126 to_screen('ERROR: unable to fetch updates')
7a5c1cfe 127 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
44f705d0 128 return
129 urlh = opener.open(url)
d5ed35b6
FV
130 newcontent = urlh.read()
131 urlh.close()
e5813e53 132 except (IOError, OSError, StopIteration):
5f6a1245 133 if verbose:
c0384f22 134 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 135 to_screen('ERROR: unable to download latest version')
7a5c1cfe 136 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
d5ed35b6
FV
137 return
138
139 try:
140 with open(exe + '.new', 'wb') as outf:
141 outf.write(newcontent)
0b63aed8 142 except (IOError, OSError):
5f6a1245 143 if verbose:
c0384f22 144 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 145 to_screen('ERROR: unable to write the new version')
d5ed35b6
FV
146 return
147
44f705d0 148 expected_sum = get_sha256sum('exe', arch)
149 if not expected_sum:
150 to_screen('WARNING: no hash information found for the release')
151 elif calc_sha256sum(exe + '.new') != expected_sum:
152 to_screen('ERROR: unable to verify the new executable')
7a5c1cfe 153 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
44f705d0 154 try:
155 os.remove(exe + '.new')
156 except OSError:
157 to_screen('ERROR: unable to remove corrupt download')
158 return
159
d5ed35b6 160 try:
3dd264bf 161 bat = os.path.join(directory, 'yt-dlp-updater.cmd')
d2790370 162 with io.open(bat, 'w') as batfile:
15938ab6 163 batfile.write('''
3dd264bf 164@(
165 echo.Waiting for file handle to be closed ...
166 ping 127.0.0.1 -n 5 -w 1000 > NUL
167 move /Y "%s.new" "%s" > NUL
e28f1c0a 168 echo.Updated yt-dlp to version %s
3dd264bf 169)
170@start /b "" cmd /c del "%%~f0"&exit /b
171 ''' % (exe, exe, version_id))
d5ed35b6 172
d2790370 173 subprocess.Popen([bat]) # Continues to run in the background
44f705d0 174 return True # Exit app
0b63aed8 175 except (IOError, OSError):
5f6a1245 176 if verbose:
c0384f22 177 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 178 to_screen('ERROR: unable to overwrite current version')
d5ed35b6
FV
179 return
180
181 # Zip unix package
182 elif isinstance(globals().get('__loader__'), zipimporter):
183 try:
e5813e53 184 py_ver = platform.python_version()[0]
44f705d0 185 url = get_bin_info('zip', py_ver).get('browser_download_url')
186 if not url:
187 to_screen('ERROR: unable to fetch updates')
7a5c1cfe 188 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
44f705d0 189 return
190 urlh = opener.open(url)
d5ed35b6
FV
191 newcontent = urlh.read()
192 urlh.close()
e5813e53 193 except (IOError, OSError, StopIteration):
5f6a1245 194 if verbose:
c0384f22 195 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 196 to_screen('ERROR: unable to download latest version')
7a5c1cfe 197 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
d5ed35b6
FV
198 return
199
44f705d0 200 expected_sum = get_sha256sum('zip', py_ver)
c4a508ab 201 if expected_sum and hashlib.sha256(newcontent).hexdigest() != expected_sum:
44f705d0 202 to_screen('ERROR: unable to verify the new zip')
7a5c1cfe 203 to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
44f705d0 204 return
205
206 try:
c4a508ab 207 with open(filename, 'wb') as outf:
208 outf.write(newcontent)
209 except (IOError, OSError):
210 if verbose:
211 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 212 to_screen('ERROR: unable to overwrite current version')
d5ed35b6
FV
213 return
214
e28f1c0a 215 to_screen('Updated yt-dlp. Restart yt-dlp to use the new version')
3bf79c75 216
5f6a1245 217
3dd264bf 218''' # UNUSED
46a127ee 219def get_notes(versions, fromVersion):
3bf79c75 220 notes = []
5f6a1245 221 for v, vdata in sorted(versions.items()):
3bf79c75
PH
222 if v > fromVersion:
223 notes.extend(vdata.get('notes', []))
46a127ee
PH
224 return notes
225
5f6a1245 226
46a127ee
PH
227def print_notes(to_screen, versions, fromVersion=__version__):
228 notes = get_notes(versions, fromVersion)
3bf79c75 229 if notes:
15938ab6 230 to_screen('PLEASE NOTE:')
3bf79c75
PH
231 for note in notes:
232 to_screen(note)
3dd264bf 233'''