]> jfr.im git - yt-dlp.git/blame - youtube_dl/update.py
pull changes from remote master (#190)
[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
b827ee92 12from .compat import compat_realpath
c0384f22 13from .utils import encode_compat_str
d3d3e2e3 14
d5ed35b6
FV
15from .version import __version__
16
5f6a1245 17
d5ed35b6 18def rsa_verify(message, signature, key):
d5ed35b6 19 from hashlib import sha256
15938ab6 20 assert isinstance(message, bytes)
4d318be1
FV
21 byte_size = (len(bin(key[0])) - 2 + 8 - 1) // 8
22 signature = ('%x' % pow(int(signature, 16), key[1], key[0])).encode()
23 signature = (byte_size * 2 - len(signature)) * b'0' + signature
24 asn1 = b'3031300d060960864801650304020105000420'
25 asn1 += sha256(message).hexdigest().encode()
26 if byte_size < len(asn1) // 2 + 11:
5f6a1245 27 return False
4d318be1
FV
28 expected = b'0001' + (byte_size - len(asn1) // 2 - 3) * b'ff' + b'00' + asn1
29 return expected == signature
d5ed35b6 30
d7386f62 31
0c3e5f49 32def update_self(to_screen, verbose, opener):
d5ed35b6
FV
33 """Update the program file with the latest version from the repository"""
34
ffbd1368 35 UPDATE_URL = 'https://yt-dl.org/update/'
d5ed35b6
FV
36 VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
37 JSON_URL = UPDATE_URL + 'versions.json'
38 UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
39
611c1dd9 40 if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
15938ab6 41 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
42 return
43
44 # Check if there is a new version
45 try:
0c3e5f49 46 newversion = opener.open(VERSION_URL).read().decode('utf-8').strip()
70a1165b 47 except Exception:
5f6a1245 48 if verbose:
c0384f22 49 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 50 to_screen('ERROR: can\'t find the current version. Please try again later.')
d5ed35b6
FV
51 return
52 if newversion == __version__:
15938ab6 53 to_screen('youtube-dl is up-to-date (' + __version__ + ')')
d5ed35b6
FV
54 return
55
56 # Download and check versions info
57 try:
0c3e5f49 58 versions_info = opener.open(JSON_URL).read().decode('utf-8')
d5ed35b6 59 versions_info = json.loads(versions_info)
70a1165b 60 except Exception:
5f6a1245 61 if verbose:
c0384f22 62 to_screen(encode_compat_str(traceback.format_exc()))
15938ab6 63 to_screen('ERROR: can\'t obtain versions info. Please try again later.')
d5ed35b6 64 return
83e865a3 65 if 'signature' not in versions_info:
15938ab6 66 to_screen('ERROR: the versions file is not signed or corrupted. Aborting.')
d5ed35b6
FV
67 return
68 signature = versions_info['signature']
69 del versions_info['signature']
70 if not rsa_verify(json.dumps(versions_info, sort_keys=True).encode('utf-8'), signature, UPDATES_RSA_KEY):
15938ab6 71 to_screen('ERROR: the versions file signature is invalid. Aborting.')
d5ed35b6
FV
72 return
73
d2790370 74 version_id = versions_info['latest']
d7386f62
PH
75
76 def version_tuple(version_str):
77 return tuple(map(int, version_str.split('.')))
2e767313 78 if version_tuple(__version__) >= version_tuple(version_id):
15938ab6 79 to_screen('youtube-dl is up to date (%s)' % __version__)
d7386f62
PH
80 return
81
15938ab6 82 to_screen('Updating to version ' + version_id + ' ...')
d2790370 83 version = versions_info['versions'][version_id]
3bf79c75 84
46a127ee 85 print_notes(to_screen, versions_info['versions'])
d5ed35b6 86
e9297256 87 # sys.executable is set to the full pathname of the exe-file for py2exe
b827ee92
AG
88 # though symlinks are not followed so that we need to do this manually
89 # with help of realpath
90 filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0])
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'):
e9297256 98 exe = filename
d5ed35b6
FV
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)