]> jfr.im git - yt-dlp.git/blame - youtube_dl/update.py
Add missing r prefix for _VALID_URLs
[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
c0384f22 12from .utils import encode_compat_str
d3d3e2e3 13
d5ed35b6
FV
14from .version import __version__
15
5f6a1245 16
d5ed35b6 17def rsa_verify(message, signature, key):
d5ed35b6 18 from hashlib import sha256
15938ab6 19 assert isinstance(message, bytes)
4d318be1
FV
20 byte_size = (len(bin(key[0])) - 2 + 8 - 1) // 8
21 signature = ('%x' % pow(int(signature, 16), key[1], key[0])).encode()
22 signature = (byte_size * 2 - len(signature)) * b'0' + signature
23 asn1 = b'3031300d060960864801650304020105000420'
24 asn1 += sha256(message).hexdigest().encode()
25 if byte_size < len(asn1) // 2 + 11:
5f6a1245 26 return False
4d318be1
FV
27 expected = b'0001' + (byte_size - len(asn1) // 2 - 3) * b'ff' + b'00' + asn1
28 return expected == signature
d5ed35b6 29
d7386f62 30
d3d3e2e3 31def update_self(to_screen, verbose, opener):
d5ed35b6
FV
32 """Update the program file with the latest version from the repository"""
33
611c1dd9 34 UPDATE_URL = 'https://rg3.github.io/youtube-dl/update/'
d5ed35b6
FV
35 VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
36 JSON_URL = UPDATE_URL + 'versions.json'
37 UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
38
611c1dd9 39 if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
15938ab6 40 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
41 return
42
43 # Check if there is a new version
44 try:
aa2fd598 45 newversion = opener.open(VERSION_URL).read().decode('utf-8').strip()
70a1165b 46 except Exception:
5f6a1245 47 if verbose:
c0384f22 48 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 49 to_screen('ERROR: can\'t find the current version. Please try again later.')
d5ed35b6
FV
50 return
51 if newversion == __version__:
15938ab6 52 to_screen('youtube-dl is up-to-date (' + __version__ + ')')
d5ed35b6
FV
53 return
54
55 # Download and check versions info
56 try:
aa2fd598 57 versions_info = opener.open(JSON_URL).read().decode('utf-8')
d5ed35b6 58 versions_info = json.loads(versions_info)
70a1165b 59 except Exception:
5f6a1245 60 if verbose:
c0384f22 61 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 62 to_screen('ERROR: can\'t obtain versions info. Please try again later.')
d5ed35b6 63 return
83e865a3 64 if 'signature' not in versions_info:
15938ab6 65 to_screen('ERROR: the versions file is not signed or corrupted. Aborting.')
d5ed35b6
FV
66 return
67 signature = versions_info['signature']
68 del versions_info['signature']
69 if not rsa_verify(json.dumps(versions_info, sort_keys=True).encode('utf-8'), signature, UPDATES_RSA_KEY):
15938ab6 70 to_screen('ERROR: the versions file signature is invalid. Aborting.')
d5ed35b6
FV
71 return
72
d2790370 73 version_id = versions_info['latest']
d7386f62
PH
74
75 def version_tuple(version_str):
76 return tuple(map(int, version_str.split('.')))
2e767313 77 if version_tuple(__version__) >= version_tuple(version_id):
15938ab6 78 to_screen('youtube-dl is up to date (%s)' % __version__)
d7386f62
PH
79 return
80
15938ab6 81 to_screen('Updating to version ' + version_id + ' ...')
d2790370 82 version = versions_info['versions'][version_id]
3bf79c75 83
46a127ee 84 print_notes(to_screen, versions_info['versions'])
d5ed35b6 85
46353f67
PH
86 filename = sys.argv[0]
87 # Py2EXE: Filename could be different
611c1dd9 88 if hasattr(sys, 'frozen') and not os.path.isfile(filename):
15938ab6
PH
89 if os.path.isfile(filename + '.exe'):
90 filename += '.exe'
46353f67 91
d5ed35b6 92 if not os.access(filename, os.W_OK):
15938ab6 93 to_screen('ERROR: no write permissions on %s' % filename)
d5ed35b6
FV
94 return
95
96 # Py2EXE
611c1dd9 97 if hasattr(sys, 'frozen'):
d5ed35b6
FV
98 exe = os.path.abspath(filename)
99 directory = os.path.dirname(exe)
100 if not os.access(directory, os.W_OK):
15938ab6 101 to_screen('ERROR: no write permissions on %s' % directory)
d5ed35b6
FV
102 return
103
104 try:
aa2fd598 105 urlh = opener.open(version['exe'][0])
d5ed35b6
FV
106 newcontent = urlh.read()
107 urlh.close()
0b63aed8 108 except (IOError, OSError):
5f6a1245 109 if verbose:
c0384f22 110 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 111 to_screen('ERROR: unable to download latest version')
d5ed35b6
FV
112 return
113
114 newcontent_hash = hashlib.sha256(newcontent).hexdigest()
115 if newcontent_hash != version['exe'][1]:
15938ab6 116 to_screen('ERROR: the downloaded file hash does not match. Aborting.')
d5ed35b6
FV
117 return
118
119 try:
120 with open(exe + '.new', 'wb') as outf:
121 outf.write(newcontent)
0b63aed8 122 except (IOError, OSError):
5f6a1245 123 if verbose:
c0384f22 124 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 125 to_screen('ERROR: unable to write the new version')
d5ed35b6
FV
126 return
127
128 try:
129 bat = os.path.join(directory, 'youtube-dl-updater.bat')
d2790370 130 with io.open(bat, 'w') as batfile:
15938ab6 131 batfile.write('''
d2790370
PH
132@echo off
133echo Waiting for file handle to be closed ...
d5ed35b6 134ping 127.0.0.1 -n 5 -w 1000 > NUL
d2790370
PH
135move /Y "%s.new" "%s" > NUL
136echo Updated youtube-dl to version %s.
137start /b "" cmd /c del "%%~f0"&exit /b"
15938ab6 138 \n''' % (exe, exe, version_id))
d5ed35b6 139
d2790370
PH
140 subprocess.Popen([bat]) # Continues to run in the background
141 return # Do not show premature success messages
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 overwrite current version')
d5ed35b6
FV
146 return
147
148 # Zip unix package
149 elif isinstance(globals().get('__loader__'), zipimporter):
150 try:
aa2fd598 151 urlh = opener.open(version['bin'][0])
d5ed35b6
FV
152 newcontent = urlh.read()
153 urlh.close()
0b63aed8 154 except (IOError, OSError):
5f6a1245 155 if verbose:
c0384f22 156 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 157 to_screen('ERROR: unable to download latest version')
d5ed35b6
FV
158 return
159
160 newcontent_hash = hashlib.sha256(newcontent).hexdigest()
161 if newcontent_hash != version['bin'][1]:
15938ab6 162 to_screen('ERROR: the downloaded file hash does not match. Aborting.')
d5ed35b6
FV
163 return
164
165 try:
166 with open(filename, 'wb') as outf:
167 outf.write(newcontent)
0b63aed8 168 except (IOError, OSError):
5f6a1245 169 if verbose:
c0384f22 170 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 171 to_screen('ERROR: unable to overwrite current version')
d5ed35b6
FV
172 return
173
15938ab6 174 to_screen('Updated youtube-dl. Restart youtube-dl to use the new version.')
3bf79c75 175
5f6a1245 176
46a127ee 177def get_notes(versions, fromVersion):
3bf79c75 178 notes = []
5f6a1245 179 for v, vdata in sorted(versions.items()):
3bf79c75
PH
180 if v > fromVersion:
181 notes.extend(vdata.get('notes', []))
46a127ee
PH
182 return notes
183
5f6a1245 184
46a127ee
PH
185def print_notes(to_screen, versions, fromVersion=__version__):
186 notes = get_notes(versions, fromVersion)
3bf79c75 187 if notes:
15938ab6 188 to_screen('PLEASE NOTE:')
3bf79c75
PH
189 for note in notes:
190 to_screen(note)