2014-11-26 12:24:57 +01:00
from __future__ import unicode_literals
2013-09-29 11:26:01 +02:00
import io
2012-12-30 19:49:14 +01:00
import json
import traceback
import hashlib
2013-11-17 16:47:52 +01:00
import os
2013-09-29 11:26:01 +02:00
import subprocess
2013-09-29 11:17:38 +02:00
import sys
2012-12-30 19:49:14 +01:00
from zipimport import zipimporter
2014-11-26 12:24:57 +01:00
from . compat import (
2013-11-17 16:47:52 +01:00
compat_str ,
compat_urllib_request ,
)
2015-01-09 20:16:55 +01:00
from . utils import make_HTTPS_handler
2012-12-30 19:49:14 +01:00
from . version import __version__
2014-11-23 20:41:03 +01:00
2012-12-30 19:49:14 +01:00
def rsa_verify ( message , signature , key ) :
from struct import pack
from hashlib import sha256
2014-11-23 20:41:03 +01:00
2014-11-26 12:24:57 +01:00
assert isinstance ( message , bytes )
2012-12-30 19:49:14 +01:00
block_size = 0
n = key [ 0 ]
while n :
block_size + = 1
n >> = 8
signature = pow ( int ( signature , 16 ) , key [ 1 ] , key [ 0 ] )
raw_bytes = [ ]
while signature :
raw_bytes . insert ( 0 , pack ( " B " , signature & 0xFF ) )
signature >> = 8
2014-11-26 12:24:57 +01:00
signature = ( block_size - len ( raw_bytes ) ) * b ' \x00 ' + b ' ' . join ( raw_bytes )
if signature [ 0 : 2 ] != b ' \x00 \x01 ' :
2014-11-23 20:41:03 +01:00
return False
2012-12-30 19:49:14 +01:00
signature = signature [ 2 : ]
2014-11-26 12:24:57 +01:00
if b ' \x00 ' not in signature :
2014-11-23 20:41:03 +01:00
return False
2014-11-26 12:24:57 +01:00
signature = signature [ signature . index ( b ' \x00 ' ) + 1 : ]
if not signature . startswith ( b ' \x30 \x31 \x30 \x0D \x06 \x09 \x60 \x86 \x48 \x01 \x65 \x03 \x04 \x02 \x01 \x05 \x00 \x04 \x20 ' ) :
2014-11-23 20:41:03 +01:00
return False
2012-12-30 19:49:14 +01:00
signature = signature [ 19 : ]
2014-11-23 20:41:03 +01:00
if signature != sha256 ( message ) . digest ( ) :
return False
2012-12-30 19:49:14 +01:00
return True
2013-11-22 23:05:56 +01:00
2013-09-29 11:17:38 +02:00
def update_self ( to_screen , verbose ) :
2012-12-30 19:49:14 +01:00
""" Update the program file with the latest version from the repository """
2015-06-07 00:19:45 +02:00
UPDATE_URL = " https://rg3.github.io/youtube-dl/update/ "
2012-12-30 19:49:14 +01:00
VERSION_URL = UPDATE_URL + ' LATEST_VERSION '
JSON_URL = UPDATE_URL + ' versions.json '
UPDATES_RSA_KEY = ( 0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93 , 65537 )
if not isinstance ( globals ( ) . get ( ' __loader__ ' ) , zipimporter ) and not hasattr ( sys , " frozen " ) :
2015-09-13 18:28:29 +01:00
to_screen ( ' It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update (eg. pip install --upgrade youtube-dl). ' )
2012-12-30 19:49:14 +01:00
return
2015-01-10 19:55:36 +01:00
https_handler = make_HTTPS_handler ( { } )
2015-01-09 20:16:55 +01:00
opener = compat_urllib_request . build_opener ( https_handler )
2012-12-30 19:49:14 +01:00
# Check if there is a new version
try :
2015-01-09 20:16:55 +01:00
newversion = opener . open ( VERSION_URL ) . read ( ) . decode ( ' utf-8 ' ) . strip ( )
2015-03-27 13:02:20 +01:00
except Exception :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: can \' t find the current version. Please try again later. ' )
2012-12-30 19:49:14 +01:00
return
if newversion == __version__ :
2014-11-26 12:24:57 +01:00
to_screen ( ' youtube-dl is up-to-date ( ' + __version__ + ' ) ' )
2012-12-30 19:49:14 +01:00
return
# Download and check versions info
try :
2015-01-09 20:16:55 +01:00
versions_info = opener . open ( JSON_URL ) . read ( ) . decode ( ' utf-8 ' )
2012-12-30 19:49:14 +01:00
versions_info = json . loads ( versions_info )
2015-03-27 13:02:20 +01:00
except Exception :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: can \' t obtain versions info. Please try again later. ' )
2012-12-30 19:49:14 +01:00
return
2014-12-09 23:11:26 +01:00
if ' signature ' not in versions_info :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: the versions file is not signed or corrupted. Aborting. ' )
2012-12-30 19:49:14 +01:00
return
signature = versions_info [ ' signature ' ]
del versions_info [ ' signature ' ]
if not rsa_verify ( json . dumps ( versions_info , sort_keys = True ) . encode ( ' utf-8 ' ) , signature , UPDATES_RSA_KEY ) :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: the versions file signature is invalid. Aborting. ' )
2012-12-30 19:49:14 +01:00
return
2013-09-29 11:26:01 +02:00
version_id = versions_info [ ' latest ' ]
2013-11-22 23:05:56 +01:00
def version_tuple ( version_str ) :
return tuple ( map ( int , version_str . split ( ' . ' ) ) )
2013-11-24 06:52:21 +01:00
if version_tuple ( __version__ ) > = version_tuple ( version_id ) :
2014-11-26 12:24:57 +01:00
to_screen ( ' youtube-dl is up to date ( %s ) ' % __version__ )
2013-11-22 23:05:56 +01:00
return
2014-11-26 12:24:57 +01:00
to_screen ( ' Updating to version ' + version_id + ' ... ' )
2013-09-29 11:26:01 +02:00
version = versions_info [ ' versions ' ] [ version_id ]
2013-02-22 00:36:23 +01:00
2013-04-28 16:19:37 +02:00
print_notes ( to_screen , versions_info [ ' versions ' ] )
2012-12-30 19:49:14 +01:00
2013-09-29 11:17:38 +02:00
filename = sys . argv [ 0 ]
# Py2EXE: Filename could be different
if hasattr ( sys , " frozen " ) and not os . path . isfile ( filename ) :
2014-11-26 12:24:57 +01:00
if os . path . isfile ( filename + ' .exe ' ) :
filename + = ' .exe '
2013-09-29 11:17:38 +02:00
2012-12-30 19:49:14 +01:00
if not os . access ( filename , os . W_OK ) :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: no write permissions on %s ' % filename )
2012-12-30 19:49:14 +01:00
return
# Py2EXE
if hasattr ( sys , " frozen " ) :
exe = os . path . abspath ( filename )
directory = os . path . dirname ( exe )
if not os . access ( directory , os . W_OK ) :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: no write permissions on %s ' % directory )
2012-12-30 19:49:14 +01:00
return
try :
2015-01-09 20:16:55 +01:00
urlh = opener . open ( version [ ' exe ' ] [ 0 ] )
2012-12-30 19:49:14 +01:00
newcontent = urlh . read ( )
urlh . close ( )
2013-11-22 19:15:36 +01:00
except ( IOError , OSError ) :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: unable to download latest version ' )
2012-12-30 19:49:14 +01:00
return
newcontent_hash = hashlib . sha256 ( newcontent ) . hexdigest ( )
if newcontent_hash != version [ ' exe ' ] [ 1 ] :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: the downloaded file hash does not match. Aborting. ' )
2012-12-30 19:49:14 +01:00
return
try :
with open ( exe + ' .new ' , ' wb ' ) as outf :
outf . write ( newcontent )
2013-11-22 19:15:36 +01:00
except ( IOError , OSError ) :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: unable to write the new version ' )
2012-12-30 19:49:14 +01:00
return
try :
bat = os . path . join ( directory , ' youtube-dl-updater.bat ' )
2013-09-29 11:26:01 +02:00
with io . open ( bat , ' w ' ) as batfile :
2014-11-26 12:24:57 +01:00
batfile . write ( '''
2013-09-29 11:26:01 +02:00
@echo off
echo Waiting for file handle to be closed . . .
2012-12-30 19:49:14 +01:00
ping 127.0 .0 .1 - n 5 - w 1000 > NUL
2013-09-29 11:26:01 +02:00
move / Y " %s .new " " %s " > NUL
echo Updated youtube - dl to version % s .
start / b " " cmd / c del " %% ~f0 " & exit / b "
2014-11-26 12:24:57 +01:00
\n ''' % (exe, exe, version_id))
2012-12-30 19:49:14 +01:00
2013-09-29 11:26:01 +02:00
subprocess . Popen ( [ bat ] ) # Continues to run in the background
return # Do not show premature success messages
2013-11-22 19:15:36 +01:00
except ( IOError , OSError ) :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: unable to overwrite current version ' )
2012-12-30 19:49:14 +01:00
return
# Zip unix package
elif isinstance ( globals ( ) . get ( ' __loader__ ' ) , zipimporter ) :
try :
2015-01-09 20:16:55 +01:00
urlh = opener . open ( version [ ' bin ' ] [ 0 ] )
2012-12-30 19:49:14 +01:00
newcontent = urlh . read ( )
urlh . close ( )
2013-11-22 19:15:36 +01:00
except ( IOError , OSError ) :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: unable to download latest version ' )
2012-12-30 19:49:14 +01:00
return
newcontent_hash = hashlib . sha256 ( newcontent ) . hexdigest ( )
if newcontent_hash != version [ ' bin ' ] [ 1 ] :
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: the downloaded file hash does not match. Aborting. ' )
2012-12-30 19:49:14 +01:00
return
try :
with open ( filename , ' wb ' ) as outf :
outf . write ( newcontent )
2013-11-22 19:15:36 +01:00
except ( IOError , OSError ) :
2014-11-23 20:41:03 +01:00
if verbose :
to_screen ( compat_str ( traceback . format_exc ( ) ) )
2014-11-26 12:24:57 +01:00
to_screen ( ' ERROR: unable to overwrite current version ' )
2012-12-30 19:49:14 +01:00
return
2014-11-26 12:24:57 +01:00
to_screen ( ' Updated youtube-dl. Restart youtube-dl to use the new version. ' )
2013-02-22 00:36:23 +01:00
2014-11-23 20:41:03 +01:00
2013-04-28 16:19:37 +02:00
def get_notes ( versions , fromVersion ) :
2013-02-22 00:36:23 +01:00
notes = [ ]
2014-11-23 20:41:03 +01:00
for v , vdata in sorted ( versions . items ( ) ) :
2013-02-22 00:36:23 +01:00
if v > fromVersion :
notes . extend ( vdata . get ( ' notes ' , [ ] ) )
2013-04-28 16:19:37 +02:00
return notes
2014-11-23 20:41:03 +01:00
2013-04-28 16:19:37 +02:00
def print_notes ( to_screen , versions , fromVersion = __version__ ) :
notes = get_notes ( versions , fromVersion )
2013-02-22 00:36:23 +01:00
if notes :
2014-11-26 12:24:57 +01:00
to_screen ( ' PLEASE NOTE: ' )
2013-02-22 00:36:23 +01:00
for note in notes :
to_screen ( note )