2424import re
2525import time
2626import argparse
27+ import ssl
28+ import contextlib
29+
30+ from urllib .request import urlretrieve
31+ from urllib .request import urlopen
32+ from urllib .response import addinfourl
33+ from typing import IO , Any , Callable , Dict , Iterator , List , Optional , Set , Tuple , Union
34+ from ssl import SSLContext
35+
36+ unicode = lambda s : str (s ) # noqa: E731
37+
38+ # the older "DigiCert Global Root CA" certificate used with github.com
39+ DIGICERT_ROOT_CA_CERT = """
40+ -----BEGIN CERTIFICATE-----
41+ MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
42+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
43+ d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
44+ QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
45+ MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
46+ b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
47+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
48+ CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
49+ nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
50+ 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
51+ T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
52+ gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
53+ BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
54+ TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
55+ DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
56+ hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
57+ 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
58+ PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
59+ YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
60+ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
61+ -----END CERTIFICATE-----
62+ """
2763
28- # Initialize start_time globally
29- start_time = - 1
30-
31- if sys .version_info [0 ] == 3 :
32- from urllib .request import urlretrieve
33- from urllib .request import urlopen
64+ # the newer "DigiCert Global Root G2" certificate used with dl.espressif.com
65+ DIGICERT_ROOT_G2_CERT = """
66+ -----BEGIN CERTIFICATE-----
67+ MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
68+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
69+ d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
70+ MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
71+ MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
72+ b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
73+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
74+ 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
75+ 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
76+ q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
77+ tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
78+ vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
79+ BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
80+ 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
81+ 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
82+ NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
83+ Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
84+ 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
85+ pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
86+ MrY=
87+ -----END CERTIFICATE-----
88+ """
3489
35- unicode = lambda s : str (s ) # noqa: E731
36- else :
37- # Not Python 3 - today, it is most likely to be Python 2
38- from urllib import urlretrieve
39- from urllib import urlopen
90+ DL_CERT_DICT = {'dl.espressif.com' : DIGICERT_ROOT_G2_CERT ,
91+ 'github.com' : DIGICERT_ROOT_CA_CERT }
4092
41- if "Windows" in platform . system ():
42- import requests
93+ # Initialize start_time globally
94+ start_time = - 1
4395
4496# determine if application is a script file or frozen exe
4597if getattr (sys , "frozen" , False ):
@@ -71,20 +123,18 @@ def format_time(seconds):
71123 return "{:02}:{:05.2f}" .format (int (minutes ), seconds )
72124
73125
74- def report_progress (block_count , block_size , total_size , start_time ):
126+ def report_progress (block_count , block_size , total_size ):
75127 downloaded_size = block_count * block_size
76- time_elapsed = time .time () - start_time
77- current_speed = downloaded_size / (time_elapsed )
78128
79129 if sys .stdout .isatty ():
80130 if total_size > 0 :
81131 percent_complete = min ((downloaded_size / total_size ) * 100 , 100 )
82132 sys .stdout .write (
83- f"\r Downloading... { percent_complete :.2f} % - { downloaded_size / 1024 / 1024 :.2f} MB downloaded - Elapsed Time: { format_time ( time_elapsed ) } - Speed: { current_speed / 1024 / 1024 :.2f } MB/s " # noqa: E501
133+ f"\r Downloading... { percent_complete :.2f} % - { downloaded_size / 1024 / 1024 :.2f} MB downloaded" # noqa: E501
84134 )
85135 else :
86136 sys .stdout .write (
87- f"\r Downloading... { downloaded_size / 1024 / 1024 :.2f} MB downloaded - Elapsed Time: { format_time ( time_elapsed ) } - Speed: { current_speed / 1024 / 1024 :.2f } MB/s " # noqa: E501
137+ f"\r Downloading... { downloaded_size / 1024 / 1024 :.2f} MB downloaded" # noqa: E501
88138 )
89139 sys .stdout .flush ()
90140
@@ -243,18 +293,25 @@ def unpack(filename, destination, force_extract, checksum): # noqa: C901
243293 if filename .endswith ("tar.gz" ):
244294 if not cfile :
245295 cfile = tarfile .open (filename , "r:gz" )
246- cfile .extractall (destination , filter = "tar " )
296+ cfile .extractall (destination , filter = "fully_trusted " )
247297 elif filename .endswith ("tar.xz" ):
248298 if not cfile :
249299 cfile = tarfile .open (filename , "r:xz" )
250- cfile .extractall (destination , filter = "tar " )
300+ cfile .extractall (destination , filter = "fully_trusted " )
251301 elif filename .endswith ("zip" ):
252302 if not cfile :
253303 cfile = zipfile .ZipFile (filename )
254304 cfile .extractall (destination )
255305 else :
256306 raise NotImplementedError ("Unsupported archive type" )
257307
308+ if sys .platform != 'win32' and filename .endswith ('zip' ) and isinstance (cfile , ZipFile ):
309+ for file_info in cfile .infolist ():
310+ extracted_file = os .path .join (destination , file_info .filename )
311+ extracted_permissions = file_info .external_attr >> 16 & 0o777 # Extract Unix permissions
312+ if os .path .exists (extracted_file ):
313+ os .chmod (extracted_file , extracted_permissions )
314+
258315 if rename_to != dirname :
259316 print ("Renaming {0} to {1} ..." .format (dirname , rename_to ))
260317 shutil .move (dirname , rename_to )
@@ -275,55 +332,67 @@ def unpack(filename, destination, force_extract, checksum): # noqa: C901
275332 return False
276333
277334
278- def download_file_with_progress (url , filename , start_time ):
279- import ssl
280- import contextlib
281-
282- ctx = ssl .create_default_context ()
283- ctx .check_hostname = False
284- ctx .verify_mode = ssl .CERT_NONE
285- with contextlib .closing (urlopen (url , context = ctx )) as fp :
286- total_size = int (fp .getheader ("Content-Length" , fp .getheader ("Content-length" , "0" )))
287- block_count = 0
288- block_size = 1024 * 8
289- block = fp .read (block_size )
290- if block :
291- with open (filename , "wb" ) as out_file :
292- out_file .write (block )
293- block_count += 1
294- report_progress (block_count , block_size , total_size , start_time )
295- while True :
296- block = fp .read (block_size )
297- if not block :
298- break
299- out_file .write (block )
300- block_count += 1
301- report_progress (block_count , block_size , total_size , start_time )
302- else :
303- raise Exception ("Non-existing file or connection error" )
304-
305-
306- def download_file (url , filename ):
307- import ssl
308- import contextlib
309-
310- ctx = ssl .create_default_context ()
311- ctx .check_hostname = False
312- ctx .verify_mode = ssl .CERT_NONE
313- with contextlib .closing (urlopen (url , context = ctx )) as fp :
314- block_size = 1024 * 8
315- block = fp .read (block_size )
316- if block :
317- with open (filename , "wb" ) as out_file :
318- out_file .write (block )
319- while True :
320- block = fp .read (block_size )
321- if not block :
322- break
323- out_file .write (block )
324- else :
325- raise Exception ("Non-existing file or connection error" )
326-
335+ def splittype (url : str ) -> Tuple [Optional [str ], str ]:
336+ """
337+ Splits given url into its type (e.g. https, file) and the rest.
338+ """
339+ match = re .match ('([^/:]+):(.*)' , url , re .DOTALL )
340+ if match :
341+ scheme , data = match .groups ()
342+ return scheme .lower (), data
343+ return None , url
344+
345+ def urlretrieve_ctx (url : str ,
346+ filename : str ,
347+ reporthook : Optional [Callable [[int , int , int ], None ]]= None ,
348+ data : Optional [bytes ]= None ,
349+ context : Optional [SSLContext ]= None ) -> Tuple [str , addinfourl ]:
350+ """
351+ Retrieve data from given URL. An alternative version of urlretrieve which takes SSL context as an argument.
352+ """
353+ url_type , path = splittype (url )
354+
355+ # urlopen doesn't have context argument in Python <=2.7.9
356+ extra_urlopen_args = {}
357+ if context :
358+ extra_urlopen_args ['context' ] = context
359+ with contextlib .closing (urlopen (url , data , ** extra_urlopen_args )) as fp : # type: ignore
360+ headers = fp .info ()
361+
362+ # Just return the local path and the "headers" for file://
363+ # URLs. No sense in performing a copy unless requested.
364+ if url_type == 'file' and not filename :
365+ return os .path .normpath (path ), headers
366+
367+ # Handle temporary file setup.
368+ tfp = open (filename , 'wb' )
369+
370+ with tfp :
371+ result = filename , headers
372+ bs = 1024 * 8
373+ size = int (headers .get ('content-length' , - 1 ))
374+ read = 0
375+ blocknum = 0
376+
377+ if reporthook :
378+ reporthook (blocknum , bs , size )
379+
380+ while True :
381+ block = fp .read (bs )
382+ if not block :
383+ break
384+ read += len (block )
385+ tfp .write (block )
386+ blocknum += 1
387+ if reporthook :
388+ reporthook (blocknum , bs , size )
389+
390+ if size >= 0 and read < size :
391+ raise ContentTooShortError (
392+ 'retrieval incomplete: got only %i out of %i bytes'
393+ % (read , size ), result )
394+
395+ return result
327396
328397def get_tool (tool , force_download , force_extract ):
329398 sys_name = platform .system ()
@@ -339,29 +408,22 @@ def get_tool(tool, force_download, force_extract):
339408 else :
340409 print ("Downloading '" + archive_name + "' ..." )
341410 sys .stdout .flush ()
342- if "CYGWIN_NT" in sys_name :
343- import ssl
344-
345- ctx = ssl .create_default_context ()
346- ctx .check_hostname = False
347- ctx .verify_mode = ssl .CERT_NONE
348- urlretrieve (url , local_path , report_progress , context = ctx )
349- elif "Windows" in sys_name :
350- r = requests .get (url )
351- f = open (local_path , "wb" )
352- f .write (r .content )
353- f .close ()
354- else :
355- is_ci = os .environ .get ("GITHUB_WORKSPACE" )
356- if is_ci :
357- download_file (url , local_path )
411+
412+ try :
413+ for site , cert in DL_CERT_DICT .items ():
414+ if site in url :
415+ ctx = ssl .create_default_context ()
416+ ctx .load_verify_locations (cadata = cert )
417+ break
358418 else :
359- try :
360- urlretrieve (url , local_path , report_progress )
361- except : # noqa: E722
362- download_file_with_progress (url , local_path , start_time )
363- sys .stdout .write (" - Done\n " )
364- sys .stdout .flush ()
419+ ctx = None
420+
421+ urlretrieve_ctx (url , local_path , report_progress , context = ctx )
422+ except Exception as e :
423+ print (f"Failed to download { archive_name } : { e } " )
424+ return False
425+ finally :
426+ sys .stdout .flush ()
365427 else :
366428 print ("Tool {0} already downloaded" .format (archive_name ))
367429 sys .stdout .flush ()
0 commit comments