11import platform , os , sys , stat , tempfile , re , subprocess
22from browserstack .bserrors import BrowserStackLocalError
3+ import gzip
34
45try :
5- from urllib .request import urlopen
6+ import urllib .request
7+
8+ def urlopen (url , headers = None ):
9+ return urllib .request .urlopen (urllib .request .Request (url , headers = headers ))
610except ImportError :
7- from urllib2 import urlopen
11+ import urllib2
12+
13+ def urlopen (url , headers = None ):
14+ return urllib2 .urlopen (urllib2 .Request (url , headers = headers ))
815
916class LocalBinary :
17+ _version = None
18+
1019 def __init__ (self ):
1120 is_64bits = sys .maxsize > 2 ** 32
1221 self .is_windows = False
@@ -32,11 +41,13 @@ def __init__(self):
3241 ]
3342 self .path_index = 0
3443
44+ @staticmethod
45+ def set_version (version ):
46+ LocalBinary ._version = version
47+
3548 def is_alpine (self ):
36- grepOutput = subprocess .run ("grep -w NAME /etc/os-release" , capture_output = True , shell = True )
37- if grepOutput .stdout .decode ('utf-8' ).find ('Alpine' ) > - 1 :
38- return True
39- return False
49+ response = subprocess .check_output (["grep" , "-w" , "NAME" , "/etc/os-release" ])
50+ return response .decode ('utf-8' ).find ('Alpine' ) > - 1
4051
4152 def __make_path (self , dest_path ):
4253 try :
@@ -57,33 +68,57 @@ def __available_dir(self):
5768 raise BrowserStackLocalError ('Error trying to download BrowserStack Local binary' )
5869
5970 def download (self , chunk_size = 8192 , progress_hook = None ):
60- response = urlopen (self .http_path )
71+ headers = {
72+ 'User-Agent' : '/' .join (('browserstack-local-python' , LocalBinary ._version )),
73+ 'Accept-Encoding' : 'gzip, *' ,
74+ }
75+
76+ if sys .version_info < (3 , 2 ):
77+ # lack of support for gzip decoding for stream, response is expected to have a tell() method
78+ headers .pop ('Accept-Encoding' , None )
79+
80+ response = urlopen (self .http_path , headers = headers )
6181 try :
62- total_size = int (response .info ().getheader ('Content-Length' ).strip ())
82+ total_size = int (response .info ().get ('Content-Length' , '' ).strip () or '0' )
6383 except :
64- total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip ())
84+ total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip () or '0' )
6585 bytes_so_far = 0
6686
6787 dest_parent_dir = self .__available_dir ()
6888 dest_binary_name = 'BrowserStackLocal'
6989 if self .is_windows :
7090 dest_binary_name += '.exe'
7191
92+ content_encoding = response .info ().get ('Content-Encoding' , '' )
93+ gzip_file = gzip .GzipFile (fileobj = response , mode = 'rb' ) if content_encoding .lower () == 'gzip' else None
94+
95+ def read_chunk (chunk_size ):
96+ if gzip_file :
97+ return gzip_file .read (chunk_size )
98+ else :
99+ return response .read (chunk_size )
100+
72101 with open (os .path .join (dest_parent_dir , dest_binary_name ), 'wb' ) as local_file :
73102 while True :
74- chunk = response . read (chunk_size )
103+ chunk = read_chunk (chunk_size )
75104 bytes_so_far += len (chunk )
76105
77106 if not chunk :
78107 break
79108
80- if progress_hook :
109+ if total_size > 0 and progress_hook :
81110 progress_hook (bytes_so_far , chunk_size , total_size )
82111
83112 try :
84113 local_file .write (chunk )
85114 except :
86115 return self .download (chunk_size , progress_hook )
116+
117+ if gzip_file :
118+ gzip_file .close ()
119+
120+ if callable (getattr (response , 'close' , None )):
121+ response .close ()
87122
88123 final_path = os .path .join (dest_parent_dir , dest_binary_name )
89124 st = os .stat (final_path )
0 commit comments