|
48 | 48 | import urllib.request
|
49 | 49 | import urllib.parse
|
50 | 50 | import hashlib
|
51 |
| -import ctypes.wintypes |
52 |
| -import requests |
53 |
| -import ssl |
54 | 51 | import wx
|
55 | 52 | import languageHandler
|
56 | 53 |
|
|
68 | 65 | import addonAPIVersion
|
69 | 66 | from logHandler import log, isPathExternalToNVDA
|
70 | 67 | import winKernel
|
| 68 | +from utils.networking import _fetchUrlAndUpdateRootCertificates |
71 | 69 | from utils.tempFile import _createEmptyTempFileForDeletingFile
|
72 | 70 | from dataclasses import dataclass
|
73 | 71 |
|
| 72 | +import NVDAState |
| 73 | + |
| 74 | + |
| 75 | +def __getattr__(attrName: str) -> Any: |
| 76 | + """Module level `__getattr__` used to preserve backward compatibility.""" |
| 77 | + if attrName == "CERT_USAGE_MATCH" and NVDAState._allowDeprecatedAPI(): |
| 78 | + log.warning( |
| 79 | + "CERT_USAGE_MATCH is deprecated and will be removed in a future version of NVDA. ", |
| 80 | + stack_info=True, |
| 81 | + ) |
| 82 | + from utils.networking import _CERT_USAGE_MATCH as CERT_USAGE_MATCH |
| 83 | + |
| 84 | + return CERT_USAGE_MATCH |
| 85 | + if attrName == "CERT_CHAIN_PARA" and NVDAState._allowDeprecatedAPI(): |
| 86 | + log.warning( |
| 87 | + "CERT_CHAIN_PARA is deprecated and will be removed in a future version of NVDA. ", |
| 88 | + stack_info=True, |
| 89 | + ) |
| 90 | + from utils.networking import _CERT_CHAIN_PARA as CERT_CHAIN_PARA |
| 91 | + |
| 92 | + return CERT_CHAIN_PARA |
| 93 | + if attrName == "UPDATE_FETCH_TIMEOUT_S" and NVDAState._allowDeprecatedAPI(): |
| 94 | + log.warning( |
| 95 | + "UPDATE_FETCH_TIMEOUT_S is deprecated and will be removed in a future version of NVDA. ", |
| 96 | + stack_info=True, |
| 97 | + ) |
| 98 | + from utils.networking import _FETCH_TIMEOUT_S as UPDATE_FETCH_TIMEOUT_S |
| 99 | + |
| 100 | + return UPDATE_FETCH_TIMEOUT_S |
| 101 | + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") |
| 102 | + |
74 | 103 |
|
75 | 104 | #: The URL to use for update checks.
|
76 | 105 | _DEFAULT_CHECK_URL = "https://api.nvaccess.org/nvdaUpdateCheck"
|
@@ -176,9 +205,6 @@ def getQualifiedDriverClassNameForStats(cls):
|
176 | 205 | return "%s (core)" % name
|
177 | 206 |
|
178 | 207 |
|
179 |
| -UPDATE_FETCH_TIMEOUT_S = 30 # seconds |
180 |
| - |
181 |
| - |
182 | 208 | def checkForUpdate(auto: bool = False) -> UpdateInfo | None:
|
183 | 209 | """Check for an updated version of NVDA.
|
184 | 210 | This will block, so it generally shouldn't be called from the main thread.
|
@@ -230,28 +256,17 @@ def checkForUpdate(auto: bool = False) -> UpdateInfo | None:
|
230 | 256 | }
|
231 | 257 | params.update(extraParams)
|
232 | 258 |
|
233 |
| - url = f"{_getCheckURL()}?{urllib.parse.urlencode(params)}" |
234 |
| - try: |
235 |
| - log.debug(f"Fetching update data from {url}") |
236 |
| - res = urllib.request.urlopen(url, timeout=UPDATE_FETCH_TIMEOUT_S) |
237 |
| - except IOError as e: |
238 |
| - if ( |
239 |
| - isinstance(e.reason, ssl.SSLCertVerificationError) |
240 |
| - and e.reason.reason == "CERTIFICATE_VERIFY_FAILED" |
241 |
| - ): |
242 |
| - # #4803: Windows fetches trusted root certificates on demand. |
243 |
| - # Python doesn't trigger this fetch (PythonIssue:20916), so try it ourselves |
244 |
| - _updateWindowsRootCertificates() |
245 |
| - # Retry the update check |
246 |
| - log.debug(f"Retrying update check from {url}") |
247 |
| - res = urllib.request.urlopen(url, timeout=UPDATE_FETCH_TIMEOUT_S) |
248 |
| - else: |
249 |
| - raise |
| 259 | + result = _fetchUrlAndUpdateRootCertificates( |
| 260 | + url=f"{_getCheckURL()}?{urllib.parse.urlencode(params)}", |
| 261 | + # We must specify versionType so the server doesn't return a 404 error and |
| 262 | + # thus cause an exception. |
| 263 | + certFetchUrl=f"{_getCheckURL()}?versionType=stable", |
| 264 | + ) |
250 | 265 |
|
251 |
| - if res.code != 200: |
252 |
| - raise RuntimeError(f"Checking for update failed with HTTP status code {res.code}.") |
| 266 | + if result.status_code != 200: |
| 267 | + raise RuntimeError(f"Checking for update failed with HTTP status code {result.status_code}.") |
253 | 268 |
|
254 |
| - data = res.read().decode("utf-8") # Ensure the response is decoded correctly |
| 269 | + data = result.content.decode("utf-8") # Ensure the response is decoded correctly |
255 | 270 | # if data is empty, we return None, because the server returns an empty response if there is no update.
|
256 | 271 | if not data:
|
257 | 272 | return None
|
@@ -1038,68 +1053,3 @@ def terminate():
|
1038 | 1053 | if autoChecker:
|
1039 | 1054 | autoChecker.terminate()
|
1040 | 1055 | autoChecker = None
|
1041 |
| - |
1042 |
| - |
1043 |
| -# These structs are only complete enough to achieve what we need. |
1044 |
| -class CERT_USAGE_MATCH(ctypes.Structure): |
1045 |
| - _fields_ = ( |
1046 |
| - ("dwType", ctypes.wintypes.DWORD), |
1047 |
| - # CERT_ENHKEY_USAGE struct |
1048 |
| - ("cUsageIdentifier", ctypes.wintypes.DWORD), |
1049 |
| - ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR * |
1050 |
| - ) |
1051 |
| - |
1052 |
| - |
1053 |
| -class CERT_CHAIN_PARA(ctypes.Structure): |
1054 |
| - _fields_ = ( |
1055 |
| - ("cbSize", ctypes.wintypes.DWORD), |
1056 |
| - ("RequestedUsage", CERT_USAGE_MATCH), |
1057 |
| - ("RequestedIssuancePolicy", CERT_USAGE_MATCH), |
1058 |
| - ("dwUrlRetrievalTimeout", ctypes.wintypes.DWORD), |
1059 |
| - ("fCheckRevocationFreshnessTime", ctypes.wintypes.BOOL), |
1060 |
| - ("dwRevocationFreshnessTime", ctypes.wintypes.DWORD), |
1061 |
| - ("pftCacheResync", ctypes.c_void_p), # LPFILETIME |
1062 |
| - ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA |
1063 |
| - ("dwStrongSignFlags", ctypes.wintypes.DWORD), |
1064 |
| - ) |
1065 |
| - |
1066 |
| - |
1067 |
| -def _updateWindowsRootCertificates(): |
1068 |
| - log.debug("Updating Windows root certificates") |
1069 |
| - crypt = ctypes.windll.crypt32 |
1070 |
| - with requests.get( |
1071 |
| - # We must specify versionType so the server doesn't return a 404 error and |
1072 |
| - # thus cause an exception. |
1073 |
| - f"{_getCheckURL()}?versionType=stable", |
1074 |
| - timeout=UPDATE_FETCH_TIMEOUT_S, |
1075 |
| - # Use an unverified connection to avoid a certificate error. |
1076 |
| - verify=False, |
1077 |
| - stream=True, |
1078 |
| - ) as response: |
1079 |
| - # Get the server certificate. |
1080 |
| - cert = response.raw.connection.sock.getpeercert(True) |
1081 |
| - # Convert to a form usable by Windows. |
1082 |
| - certCont = crypt.CertCreateCertificateContext( |
1083 |
| - 0x00000001, # X509_ASN_ENCODING |
1084 |
| - cert, |
1085 |
| - len(cert), |
1086 |
| - ) |
1087 |
| - # Ask Windows to build a certificate chain, thus triggering a root certificate update. |
1088 |
| - chainCont = ctypes.c_void_p() |
1089 |
| - crypt.CertGetCertificateChain( |
1090 |
| - None, |
1091 |
| - certCont, |
1092 |
| - None, |
1093 |
| - None, |
1094 |
| - ctypes.byref( |
1095 |
| - CERT_CHAIN_PARA( |
1096 |
| - cbSize=ctypes.sizeof(CERT_CHAIN_PARA), |
1097 |
| - RequestedUsage=CERT_USAGE_MATCH(), |
1098 |
| - ), |
1099 |
| - ), |
1100 |
| - 0, |
1101 |
| - None, |
1102 |
| - ctypes.byref(chainCont), |
1103 |
| - ) |
1104 |
| - crypt.CertFreeCertificateChain(chainCont) |
1105 |
| - crypt.CertFreeCertificateContext(certCont) |
0 commit comments