eric6/WebBrowser/Network/NetworkManager.py

changeset 6942
2602857055c5
parent 6891
93f82da09f22
child 6989
8b8cadf8d7e9
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a network manager class.
8 """
9
10 from __future__ import unicode_literals
11
12 import json
13
14 from PyQt5.QtCore import pyqtSignal, QByteArray
15 from PyQt5.QtWidgets import qApp, QStyle, QDialog
16 from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkProxy, \
17 QNetworkProxyFactory, QNetworkRequest
18
19 from E5Gui import E5MessageBox
20
21 from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired
22 try:
23 from E5Network.E5SslErrorHandler import E5SslErrorHandler
24 SSL_AVAILABLE = True
25 except ImportError:
26 SSL_AVAILABLE = False
27
28 from WebBrowser.WebBrowserWindow import WebBrowserWindow
29 from .NetworkUrlInterceptor import NetworkUrlInterceptor
30 from ..Tools.WebBrowserTools import readAllFileContents, pixmapToDataUrl
31
32 from Globals import qVersionTuple
33 from Utilities.AutoSaver import AutoSaver
34 import Preferences
35
36
37 class NetworkManager(QNetworkAccessManager):
38 """
39 Class implementing a network manager.
40
41 @signal changed() emitted to indicate a change
42 """
43 changed = pyqtSignal()
44
45 def __init__(self, engine, parent=None):
46 """
47 Constructor
48
49 @param engine reference to the help engine (QHelpEngine)
50 @param parent reference to the parent object (QObject)
51 """
52 super(NetworkManager, self).__init__(parent)
53
54 from E5Network.E5NetworkProxyFactory import E5NetworkProxyFactory
55
56 self.__proxyFactory = E5NetworkProxyFactory()
57 if Preferences.getUI("UseSystemProxy"):
58 QNetworkProxyFactory.setUseSystemConfiguration(True)
59 else:
60 QNetworkProxyFactory.setApplicationProxyFactory(
61 self.__proxyFactory)
62 QNetworkProxyFactory.setUseSystemConfiguration(False)
63
64 self.languagesChanged()
65
66 if SSL_AVAILABLE:
67 self.__sslErrorHandler = E5SslErrorHandler(self)
68 self.sslErrors.connect(self.__sslErrorHandler.sslErrorsReplySlot)
69
70 self.__temporarilyIgnoredSslErrors = {}
71 self.__permanentlyIgnoredSslErrors = {}
72 # dictionaries of permanently and temporarily ignored SSL errors
73
74 self.__loaded = False
75 self.__saveTimer = AutoSaver(self, self.__save)
76
77 self.changed.connect(self.__saveTimer.changeOccurred)
78 self.proxyAuthenticationRequired.connect(proxyAuthenticationRequired)
79 self.authenticationRequired.connect(
80 lambda reply, auth: self.authentication(reply.url(), auth))
81
82 if qVersionTuple() >= (5, 12, 0):
83 from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
84 scheme = QWebEngineUrlScheme(b"eric")
85 scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path)
86 scheme.setFlags(QWebEngineUrlScheme.SecureScheme |
87 QWebEngineUrlScheme.ContentSecurityPolicyIgnored)
88 QWebEngineUrlScheme.registerScheme(scheme)
89 from .EricSchemeHandler import EricSchemeHandler
90 self.__ericSchemeHandler = EricSchemeHandler()
91 WebBrowserWindow.webProfile().installUrlSchemeHandler(
92 QByteArray(b"eric"), self.__ericSchemeHandler)
93
94 if engine:
95 if qVersionTuple() >= (5, 12, 0):
96 from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
97 scheme = QWebEngineUrlScheme(b"qthelp")
98 scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path)
99 scheme.setFlags(QWebEngineUrlScheme.SecureScheme)
100 QWebEngineUrlScheme.registerScheme(scheme)
101 from .QtHelpSchemeHandler import QtHelpSchemeHandler
102 self.__qtHelpSchemeHandler = QtHelpSchemeHandler(engine)
103 WebBrowserWindow.webProfile().installUrlSchemeHandler(
104 QByteArray(b"qthelp"), self.__qtHelpSchemeHandler)
105
106 self.__interceptor = NetworkUrlInterceptor(self)
107 WebBrowserWindow.webProfile().setRequestInterceptor(self.__interceptor)
108
109 WebBrowserWindow.cookieJar()
110
111 def __save(self):
112 """
113 Private slot to save the permanent SSL error exceptions.
114 """
115 if not self.__loaded:
116 return
117
118 from WebBrowser.WebBrowserWindow import WebBrowserWindow
119 if not WebBrowserWindow.isPrivate():
120 dbString = json.dumps(self.__permanentlyIgnoredSslErrors)
121 Preferences.setWebBrowser("SslExceptionsDB", dbString)
122
123 def __load(self):
124 """
125 Private method to load the permanent SSL error exceptions.
126 """
127 if self.__loaded:
128 return
129
130 dbString = Preferences.getWebBrowser("SslExceptionsDB")
131 if dbString:
132 try:
133 db = json.loads(dbString)
134 self.__permanentlyIgnoredSslErrors = db
135 except ValueError:
136 # ignore silently
137 pass
138
139 self.__loaded = True
140
141 def shutdown(self):
142 """
143 Public method to shut down the network manager.
144 """
145 self.__saveTimer.saveIfNeccessary()
146 self.__loaded = False
147 self.__temporarilyIgnoredSslErrors = {}
148 self.__permanentlyIgnoredSslErrors = {}
149
150 # set proxy factory to None to avoid crashes
151 QNetworkProxyFactory.setApplicationProxyFactory(None)
152
153 def showSslErrorExceptionsDialog(self):
154 """
155 Public method to show the SSL error exceptions dialog.
156 """
157 self.__load()
158
159 from .SslErrorExceptionsDialog import SslErrorExceptionsDialog
160 dlg = SslErrorExceptionsDialog(self.__permanentlyIgnoredSslErrors)
161 if dlg.exec_() == QDialog.Accepted:
162 self.__permanentlyIgnoredSslErrors = dlg.getSslErrorExceptions()
163 self.changed.emit()
164
165 def clearSslExceptions(self):
166 """
167 Public method to clear the permanent SSL certificate error exceptions.
168 """
169 self.__load()
170
171 self.__permanentlyIgnoredSslErrors = {}
172 self.changed.emit()
173 self.__saveTimer.saveIfNeccessary()
174
175 def certificateError(self, error, view):
176 """
177 Public method to handle SSL certificate errors.
178
179 @param error object containing the certificate error information
180 @type QWebEngineCertificateError
181 @param view reference to a view to be used as parent for the dialog
182 @type QWidget
183 @return flag indicating to ignore this error
184 @rtype bool
185 """
186 self.__load()
187
188 host = error.url().host()
189
190 if host in self.__temporarilyIgnoredSslErrors and \
191 error.error() in self.__temporarilyIgnoredSslErrors[host]:
192 return True
193
194 if host in self.__permanentlyIgnoredSslErrors and \
195 error.error() in self.__permanentlyIgnoredSslErrors[host]:
196 return True
197
198 title = self.tr("SSL Certificate Error")
199 msgBox = E5MessageBox.E5MessageBox(
200 E5MessageBox.Warning,
201 title,
202 self.tr("""<b>{0}</b>"""
203 """<p>The page you are trying to access has errors"""
204 """ in the SSL certificate.</p>"""
205 """<ul><li>{1}</li></ul>"""
206 """<p>Would you like to make an exception?</p>""")
207 .format(title, error.errorDescription()),
208 modal=True, parent=view)
209 permButton = msgBox.addButton(self.tr("&Permanent accept"),
210 E5MessageBox.AcceptRole)
211 tempButton = msgBox.addButton(self.tr("&Temporary accept"),
212 E5MessageBox.AcceptRole)
213 msgBox.addButton(self.tr("&Reject"), E5MessageBox.RejectRole)
214 msgBox.exec_()
215 if msgBox.clickedButton() == permButton:
216 if host not in self.__permanentlyIgnoredSslErrors:
217 self.__permanentlyIgnoredSslErrors[host] = []
218 self.__permanentlyIgnoredSslErrors[host].append(error.error())
219 self.changed.emit()
220 return True
221 elif msgBox.clickedButton() == tempButton:
222 if host not in self.__temporarilyIgnoredSslErrors:
223 self.__temporarilyIgnoredSslErrors[host] = []
224 self.__temporarilyIgnoredSslErrors[host].append(error.error())
225 return True
226 else:
227 return False
228
229 def authentication(self, url, auth, page=None):
230 """
231 Public slot to handle an authentication request.
232
233 @param url URL requesting authentication
234 @type QUrl
235 @param auth reference to the authenticator object
236 @type QAuthenticator
237 @param page reference to the web page
238 @type QWebEnginePage or None
239 """
240 urlRoot = "{0}://{1}"\
241 .format(url.scheme(), url.authority())
242 realm = auth.realm()
243 if not realm and 'realm' in auth.options():
244 realm = auth.option("realm")
245 if realm:
246 info = self.tr("<b>Enter username and password for '{0}', "
247 "realm '{1}'</b>").format(urlRoot, realm)
248 else:
249 info = self.tr("<b>Enter username and password for '{0}'</b>")\
250 .format(urlRoot)
251
252 from UI.AuthenticationDialog import AuthenticationDialog
253 import WebBrowser.WebBrowserWindow
254
255 dlg = AuthenticationDialog(info, auth.user(),
256 Preferences.getUser("SavePasswords"),
257 Preferences.getUser("SavePasswords"))
258 if Preferences.getUser("SavePasswords"):
259 username, password = \
260 WebBrowser.WebBrowserWindow.WebBrowserWindow.passwordManager()\
261 .getLogin(url, realm)
262 if username:
263 dlg.setData(username, password)
264 if dlg.exec_() == QDialog.Accepted:
265 username, password = dlg.getData()
266 auth.setUser(username)
267 auth.setPassword(password)
268 if Preferences.getUser("SavePasswords") and dlg.shallSave():
269 WebBrowser.WebBrowserWindow.WebBrowserWindow.passwordManager()\
270 .setLogin(url, realm, username, password)
271 else:
272 if page is not None:
273 self.__showAuthenticationErrorPage(page, url)
274
275 def __showAuthenticationErrorPage(self, page, url):
276 """
277 Private method to show an authentication error page.
278
279 @param page reference to the page
280 @type QWebEnginePage
281 @param url reference to the URL requesting authentication
282 @type QUrl
283 """
284 html = readAllFileContents(":/html/authenticationErrorPage.html")
285 html = html.replace("@IMAGE@", pixmapToDataUrl(
286 qApp.style().standardIcon(QStyle.SP_MessageBoxCritical).pixmap(
287 48, 48)).toString())
288 html = html.replace("@FAVICON@", pixmapToDataUrl(
289 qApp.style() .standardIcon(QStyle.SP_MessageBoxCritical).pixmap(
290 16, 16)).toString())
291 html = html.replace("@TITLE@", self.tr("Authentication required"))
292 html = html.replace("@H1@", self.tr("Authentication required"))
293 html = html.replace(
294 "@LI-1@",
295 self.tr("Authentication is required to access:"))
296 html = html.replace(
297 "@LI-2@",
298 '<a href="{0}">{0}</a>'.format(url.toString()))
299 page.setHtml(html, url)
300
301 def proxyAuthentication(self, requestUrl, auth, proxyHost):
302 """
303 Public slot to handle a proxy authentication request.
304
305 @param requestUrl requested URL
306 @type QUrl
307 @param auth reference to the authenticator object
308 @type QAuthenticator
309 @param proxyHost name of the proxy host
310 @type str
311 """
312 proxy = QNetworkProxy.applicationProxy()
313 if proxy.user() and proxy.password():
314 auth.setUser(proxy.user())
315 auth.setPassword(proxy.password())
316 return
317
318 proxyAuthenticationRequired(proxy, auth)
319
320 def languagesChanged(self):
321 """
322 Public slot to (re-)load the list of accepted languages.
323 """
324 from WebBrowser.WebBrowserLanguagesDialog import \
325 WebBrowserLanguagesDialog
326 languages = Preferences.toList(
327 Preferences.Prefs.settings.value(
328 "WebBrowser/AcceptLanguages",
329 WebBrowserLanguagesDialog.defaultAcceptLanguages()))
330 self.__acceptLanguage = WebBrowserLanguagesDialog.httpString(languages)
331
332 WebBrowserWindow.webProfile().setHttpAcceptLanguage(
333 self.__acceptLanguage)
334
335 def installUrlInterceptor(self, interceptor):
336 """
337 Public method to install an URL interceptor.
338
339 @param interceptor URL interceptor to be installed
340 @type UrlInterceptor
341 """
342 self.__interceptor.installUrlInterceptor(interceptor)
343
344 def removeUrlInterceptor(self, interceptor):
345 """
346 Public method to remove an URL interceptor.
347
348 @param interceptor URL interceptor to be removed
349 @type UrlInterceptor
350 """
351 self.__interceptor.removeUrlInterceptor(interceptor)
352
353 def preferencesChanged(self):
354 """
355 Public slot to handle a change of preferences.
356 """
357 self.__interceptor.preferencesChanged()
358
359 if Preferences.getUI("UseSystemProxy"):
360 QNetworkProxyFactory.setUseSystemConfiguration(True)
361 else:
362 QNetworkProxyFactory.setApplicationProxyFactory(
363 self.__proxyFactory)
364 QNetworkProxyFactory.setUseSystemConfiguration(False)
365
366 def createRequest(self, op, request, data):
367 """
368 Public method to launch a network action.
369
370 @param op operation to be performed
371 @type QNetworkAccessManager.Operation
372 @param request request to be operated on
373 @type QNetworkRequest
374 @param data reference to the data to be sent
375 @type QIODevice
376 @return reference to the network reply
377 @rtype QNetworkReply
378 """
379 req = QNetworkRequest(request)
380 req.setAttribute(QNetworkRequest.SpdyAllowedAttribute, True)
381 req.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
382
383 return super(NetworkManager, self).createRequest(op, req, data)

eric ide

mercurial