|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a network proxy factory. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import re |
|
12 |
|
13 from PyQt6.QtCore import QUrl, QCoreApplication |
|
14 from PyQt6.QtWidgets import QDialog |
|
15 from PyQt6.QtNetwork import ( |
|
16 QNetworkProxyFactory, QNetworkProxy, QNetworkProxyQuery |
|
17 ) |
|
18 |
|
19 from EricWidgets import EricMessageBox |
|
20 |
|
21 import Preferences |
|
22 import Globals |
|
23 import Utilities |
|
24 |
|
25 |
|
26 def schemeFromProxyType(proxyType): |
|
27 """ |
|
28 Module function to determine the scheme name from the proxy type. |
|
29 |
|
30 @param proxyType type of the proxy (QNetworkProxy.ProxyType) |
|
31 @return scheme (string, one of Http, Https, Ftp) |
|
32 """ |
|
33 scheme = "" |
|
34 if proxyType == QNetworkProxy.ProxyType.HttpProxy: |
|
35 scheme = "Http" |
|
36 elif proxyType == QNetworkProxy.ProxyType.HttpCachingProxy: |
|
37 scheme = "Https" |
|
38 elif proxyType == QNetworkProxy.ProxyType.FtpCachingProxy: |
|
39 scheme = "Ftp" |
|
40 elif proxyType == QNetworkProxy.ProxyType.NoProxy: |
|
41 scheme = "NoProxy" |
|
42 return scheme |
|
43 |
|
44 |
|
45 def proxyAuthenticationRequired(proxy, auth): |
|
46 """ |
|
47 Module slot to handle a proxy authentication request. |
|
48 |
|
49 @param proxy reference to the proxy object (QNetworkProxy) |
|
50 @param auth reference to the authenticator object (QAuthenticator) |
|
51 """ |
|
52 info = QCoreApplication.translate( |
|
53 "EricNetworkProxyFactory", |
|
54 "<b>Connect to proxy '{0}' using:</b>" |
|
55 ).format(Utilities.html_encode(proxy.hostName())) |
|
56 |
|
57 from UI.AuthenticationDialog import AuthenticationDialog |
|
58 dlg = AuthenticationDialog(info, proxy.user(), True) |
|
59 dlg.setData(proxy.user(), proxy.password()) |
|
60 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
61 username, password = dlg.getData() |
|
62 auth.setUser(username) |
|
63 auth.setPassword(password) |
|
64 if dlg.shallSave(): |
|
65 scheme = schemeFromProxyType(proxy.type()) |
|
66 if scheme and scheme != "NoProxy": |
|
67 Preferences.setUI("ProxyUser/{0}".format(scheme), username) |
|
68 Preferences.setUI("ProxyPassword/{0}".format(scheme), password) |
|
69 proxy.setUser(username) |
|
70 proxy.setPassword(password) |
|
71 |
|
72 |
|
73 class HostnameMatcher: |
|
74 """ |
|
75 Class implementing a matcher for host names. |
|
76 """ |
|
77 def __init__(self, pattern): |
|
78 """ |
|
79 Constructor |
|
80 |
|
81 @param pattern pattern to be matched against |
|
82 @type str |
|
83 """ |
|
84 self.__regExp = None |
|
85 self.setPattern(pattern) |
|
86 |
|
87 def setPattern(self, pattern): |
|
88 """ |
|
89 Public method to set the match pattern. |
|
90 |
|
91 @param pattern pattern to be matched against |
|
92 """ |
|
93 self.__pattern = pattern |
|
94 |
|
95 if "?" in pattern or "*" in pattern: |
|
96 regexp = "^.*{0}.*$".format( |
|
97 pattern |
|
98 .replace(".", "\\.") |
|
99 .replace("*", ".*") |
|
100 .replace("?", ".") |
|
101 ) |
|
102 self.__regExp = re.compile(regexp, re.IGNORECASE) |
|
103 |
|
104 def pattern(self): |
|
105 """ |
|
106 Public method to get the match pattern. |
|
107 |
|
108 @return match pattern |
|
109 @rtype str |
|
110 """ |
|
111 return self.__pattern |
|
112 |
|
113 def match(self, host): |
|
114 """ |
|
115 Public method to test the given string. |
|
116 |
|
117 @param host host name to be matched |
|
118 @type str |
|
119 @return flag indicating a successful match |
|
120 @rtype bool |
|
121 """ |
|
122 if self.__regExp is None: |
|
123 return self.__pattern in host |
|
124 |
|
125 return self.__regExp.search(host) is not None |
|
126 |
|
127 |
|
128 class EricNetworkProxyFactory(QNetworkProxyFactory): |
|
129 """ |
|
130 Class implementing a network proxy factory. |
|
131 """ |
|
132 def __init__(self): |
|
133 """ |
|
134 Constructor |
|
135 """ |
|
136 super().__init__() |
|
137 |
|
138 self.__hostnameMatchers = [] |
|
139 self.__exceptions = "" |
|
140 |
|
141 def __setExceptions(self, exceptions): |
|
142 """ |
|
143 Private method to set the host name exceptions. |
|
144 |
|
145 @param exceptions list of exceptions separated by ',' |
|
146 @type str |
|
147 """ |
|
148 self.__hostnameMatchers = [] |
|
149 self.__exceptions = exceptions |
|
150 for exception in self.__exceptions.split(","): |
|
151 self.__hostnameMatchers.append(HostnameMatcher(exception.strip())) |
|
152 |
|
153 def queryProxy(self, query): |
|
154 """ |
|
155 Public method to determine a proxy for a given query. |
|
156 |
|
157 @param query reference to the query object (QNetworkProxyQuery) |
|
158 @return list of proxies in order of preference (list of QNetworkProxy) |
|
159 """ |
|
160 if ( |
|
161 query.queryType() == QNetworkProxyQuery.QueryType.UrlRequest and |
|
162 query.protocolTag() in ["http", "https", "ftp"] |
|
163 ): |
|
164 # use proxy at all ? |
|
165 if not Preferences.getUI("UseProxy"): |
|
166 return [QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)] |
|
167 |
|
168 # test for exceptions |
|
169 exceptions = Preferences.getUI("ProxyExceptions") |
|
170 if exceptions != self.__exceptions: |
|
171 self.__setExceptions(exceptions) |
|
172 urlHost = query.url().host() |
|
173 for matcher in self.__hostnameMatchers: |
|
174 if matcher.match(urlHost): |
|
175 return [QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)] |
|
176 |
|
177 # determine proxy |
|
178 if Preferences.getUI("UseSystemProxy"): |
|
179 proxyList = QNetworkProxyFactory.systemProxyForQuery(query) |
|
180 if ( |
|
181 not Globals.isWindowsPlatform() and |
|
182 len(proxyList) == 1 and |
|
183 proxyList[0].type() == QNetworkProxy.ProxyType.NoProxy |
|
184 ): |
|
185 # try it the Python way |
|
186 # scan the environment for variables named <scheme>_proxy |
|
187 # scan over whole environment to make this case insensitive |
|
188 for name, value in os.environ.items(): |
|
189 name = name.lower() |
|
190 if ( |
|
191 value and |
|
192 name[-6:] == '_proxy' and |
|
193 name[:-6] == query.protocolTag().lower() |
|
194 ): |
|
195 url = QUrl(value) |
|
196 if url.scheme() in ["http", "https"]: |
|
197 proxyType = QNetworkProxy.ProxyType.HttpProxy |
|
198 elif url.scheme() == "ftp": |
|
199 proxyType = ( |
|
200 QNetworkProxy.ProxyType.FtpCachingProxy |
|
201 ) |
|
202 else: |
|
203 proxyType = QNetworkProxy.ProxyType.HttpProxy |
|
204 proxy = QNetworkProxy( |
|
205 proxyType, url.host(), url.port(), |
|
206 url.userName(), url.password()) |
|
207 proxyList = [proxy] |
|
208 break |
|
209 if proxyList: |
|
210 scheme = schemeFromProxyType(proxyList[0].type()) |
|
211 if scheme == "": |
|
212 scheme = "Http" |
|
213 if scheme != "NoProxy": |
|
214 proxyList[0].setUser( |
|
215 Preferences.getUI("ProxyUser/{0}".format(scheme))) |
|
216 proxyList[0].setPassword( |
|
217 Preferences.getUI( |
|
218 "ProxyPassword/{0}".format(scheme))) |
|
219 return proxyList |
|
220 else: |
|
221 return [QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)] |
|
222 else: |
|
223 if Preferences.getUI("UseHttpProxyForAll"): |
|
224 protocolKey = "Http" |
|
225 else: |
|
226 protocolKey = query.protocolTag().capitalize() |
|
227 host = Preferences.getUI("ProxyHost/{0}".format(protocolKey)) |
|
228 if not host: |
|
229 EricMessageBox.critical( |
|
230 None, |
|
231 QCoreApplication.translate( |
|
232 "EricNetworkProxyFactory", |
|
233 "Proxy Configuration Error"), |
|
234 QCoreApplication.translate( |
|
235 "EricNetworkProxyFactory", |
|
236 """Proxy usage was activated""" |
|
237 """ but no proxy host for protocol""" |
|
238 """ '{0}' configured.""").format(protocolKey)) |
|
239 return [ |
|
240 QNetworkProxy(QNetworkProxy.ProxyType.DefaultProxy) |
|
241 ] |
|
242 else: |
|
243 if protocolKey in ["Http", "Https", "Ftp"]: |
|
244 if query.protocolTag() == "ftp": |
|
245 proxyType = QNetworkProxy.ProxyType.FtpCachingProxy |
|
246 else: |
|
247 proxyType = QNetworkProxy.ProxyType.HttpProxy |
|
248 proxy = QNetworkProxy( |
|
249 proxyType, host, |
|
250 Preferences.getUI("ProxyPort/" + protocolKey), |
|
251 Preferences.getUI("ProxyUser/" + protocolKey), |
|
252 Preferences.getUI("ProxyPassword/" + protocolKey)) |
|
253 else: |
|
254 proxy = QNetworkProxy( |
|
255 QNetworkProxy.ProxyType.DefaultProxy) |
|
256 return [ |
|
257 proxy, |
|
258 QNetworkProxy(QNetworkProxy.ProxyType.DefaultProxy) |
|
259 ] |
|
260 else: |
|
261 return [QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)] |