|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a QNetworkAccessManager subclass. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt4.QtCore import * |
|
13 from PyQt4.QtGui import QDialog, QMessageBox |
|
14 from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest, \ |
|
15 QNetworkProxy |
|
16 try: |
|
17 from PyQt4.QtNetwork import QSsl, QSslCertificate, QSslConfiguration, QSslSocket |
|
18 SSL_AVAILABLE = True |
|
19 except ImportError: |
|
20 SSL_AVAILABLE = False |
|
21 |
|
22 from UI.AuthenticationDialog import AuthenticationDialog |
|
23 import UI.PixmapCache |
|
24 |
|
25 from Helpviewer.HelpLanguagesDialog import HelpLanguagesDialog |
|
26 import Helpviewer.HelpWindow |
|
27 |
|
28 from NetworkReply import NetworkReply |
|
29 from NetworkProtocolUnknownErrorReply import NetworkProtocolUnknownErrorReply |
|
30 from NetworkDiskCache import NetworkDiskCache |
|
31 |
|
32 from QtHelpAccessHandler import QtHelpAccessHandler |
|
33 from PyrcAccessHandler import PyrcAccessHandler |
|
34 from AboutAccessHandler import AboutAccessHandler |
|
35 |
|
36 from Helpviewer.AdBlock.AdBlockAccessHandler import AdBlockAccessHandler |
|
37 |
|
38 import Preferences |
|
39 import Utilities |
|
40 |
|
41 class NetworkAccessManager(QNetworkAccessManager): |
|
42 """ |
|
43 Class implementing a QNetworkAccessManager subclass. |
|
44 |
|
45 @signal requestCreated(QNetworkAccessManager::Operation, const QNetworkRequest&, QNetworkReply*) |
|
46 emitted after the request has been created |
|
47 """ |
|
48 def __init__(self, engine, parent = None): |
|
49 """ |
|
50 Constructor |
|
51 |
|
52 @param engine reference to the help engine (QHelpEngine) |
|
53 @param parent reference to the parent object (QObject) |
|
54 """ |
|
55 QNetworkAccessManager.__init__(self, parent) |
|
56 |
|
57 self.__adblockNetwork = None |
|
58 |
|
59 self.__schemeHandlers = {} # dictionary of scheme handlers |
|
60 |
|
61 self.__setAccessManagerProxy() |
|
62 self.__setDiskCache() |
|
63 self.languagesChanged() |
|
64 |
|
65 if SSL_AVAILABLE: |
|
66 sslCfg = QSslConfiguration.defaultConfiguration() |
|
67 caList = sslCfg.caCertificates() |
|
68 caNew = QSslCertificate.fromData(Preferences.Prefs.settings\ |
|
69 .value("Help/CaCertificates").toByteArray()) |
|
70 for cert in caNew: |
|
71 caList.append(cert) |
|
72 sslCfg.setCaCertificates(caList) |
|
73 QSslConfiguration.setDefaultConfiguration(sslCfg) |
|
74 |
|
75 self.connect(self, |
|
76 SIGNAL('sslErrors(QNetworkReply *, const QList<QSslError> &)'), |
|
77 self.__sslErrors) |
|
78 |
|
79 self.connect(self, |
|
80 SIGNAL('proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)'), |
|
81 self.__proxyAuthenticationRequired) |
|
82 self.connect(self, |
|
83 SIGNAL('authenticationRequired(QNetworkReply *, QAuthenticator *)'), |
|
84 self.__authenticationRequired) |
|
85 |
|
86 # register scheme handlers |
|
87 self.setSchemeHandler("qthelp", QtHelpAccessHandler(engine, self)) |
|
88 self.setSchemeHandler("pyrc", PyrcAccessHandler(self)) |
|
89 self.setSchemeHandler("about", AboutAccessHandler(self)) |
|
90 self.setSchemeHandler("abp", AdBlockAccessHandler(self)) |
|
91 |
|
92 def setSchemeHandler(self, scheme, handler): |
|
93 """ |
|
94 Public method to register a scheme handler. |
|
95 |
|
96 @param scheme access scheme (string) |
|
97 @param handler reference to the scheme handler object (SchemeAccessHandler) |
|
98 """ |
|
99 self.__schemeHandlers[scheme] = handler |
|
100 |
|
101 def createRequest(self, op, request, outgoingData = None): |
|
102 """ |
|
103 Protected method to create a request. |
|
104 |
|
105 @param op the operation to be performed (QNetworkAccessManager.Operation) |
|
106 @param request reference to the request object (QNetworkRequest) |
|
107 @param outgoingData reference to an IODevice containing data to be sent |
|
108 (QIODevice) |
|
109 @return reference to the created reply object (QNetworkReply) |
|
110 """ |
|
111 scheme = request.url().scheme() |
|
112 if scheme == "https" and (not SSL_AVAILABLE or not QSslSocket.supportsSsl()): |
|
113 return NetworkProtocolUnknownErrorReply(scheme) |
|
114 |
|
115 if op == QNetworkAccessManager.PostOperation and outgoingData is not None: |
|
116 outgoingDataByteArray = outgoingData.peek(1024 * 1024) |
|
117 Helpviewer.HelpWindow.HelpWindow.passwordManager().post( |
|
118 request, outgoingDataByteArray) |
|
119 |
|
120 reply = None |
|
121 if scheme in self.__schemeHandlers: |
|
122 reply = self.__schemeHandlers[scheme]\ |
|
123 .createRequest(op, request, outgoingData) |
|
124 if reply is not None: |
|
125 return reply |
|
126 |
|
127 if not self.__acceptLanguage.isEmpty(): |
|
128 req = QNetworkRequest(request) |
|
129 req.setRawHeader("Accept-Language", self.__acceptLanguage) |
|
130 else: |
|
131 req = request |
|
132 |
|
133 # AdBlock code |
|
134 if op == QNetworkAccessManager.GetOperation: |
|
135 if self.__adblockNetwork is None: |
|
136 self.__adblockNetwork = \ |
|
137 Helpviewer.HelpWindow.HelpWindow.adblockManager().network() |
|
138 reply = self.__adblockNetwork.block(req) |
|
139 if reply is not None: |
|
140 return reply |
|
141 |
|
142 reply = QNetworkAccessManager.createRequest(self, op, req, outgoingData) |
|
143 self.emit(SIGNAL("requestCreated(QNetworkAccessManager::Operation, const QNetworkRequest&, QNetworkReply*)"), |
|
144 op, req, reply) |
|
145 |
|
146 return reply |
|
147 |
|
148 def __setAccessManagerProxy(self): |
|
149 """ |
|
150 Private method to set the proxy used by the network access manager. |
|
151 """ |
|
152 if Preferences.getUI("UseProxy"): |
|
153 host = Preferences.getUI("ProxyHost") |
|
154 if not host: |
|
155 QMessageBox.critical(None, |
|
156 self.trUtf8("Web Browser"), |
|
157 self.trUtf8("""Proxy usage was activated""" |
|
158 """ but no proxy host configured.""")) |
|
159 return |
|
160 else: |
|
161 pProxyType = Preferences.getUI("ProxyType") |
|
162 if pProxyType == 0: |
|
163 proxyType = QNetworkProxy.HttpProxy |
|
164 elif pProxyType == 1: |
|
165 proxyType = QNetworkProxy.HttpCachingProxy |
|
166 elif pProxyType == 2: |
|
167 proxyType = QNetworkProxy.Socks5Proxy |
|
168 self.__proxy = QNetworkProxy(proxyType, host, |
|
169 Preferences.getUI("ProxyPort"), |
|
170 Preferences.getUI("ProxyUser"), |
|
171 Preferences.getUI("ProxyPassword")) |
|
172 self.__proxy.setCapabilities(QNetworkProxy.Capabilities( |
|
173 QNetworkProxy.CachingCapability | \ |
|
174 QNetworkProxy.HostNameLookupCapability)) |
|
175 else: |
|
176 self.__proxy = QNetworkProxy(QNetworkProxy.NoProxy) |
|
177 self.setProxy(self.__proxy) |
|
178 |
|
179 def __authenticationRequired(self, reply, auth): |
|
180 """ |
|
181 Private slot to handle an authentication request. |
|
182 |
|
183 @param reply reference to the reply object (QNetworkReply) |
|
184 @param auth reference to the authenticator object (QAuthenticator) |
|
185 """ |
|
186 urlRoot = "{0}://{1}"\ |
|
187 .format(reply.url().scheme(), reply.url().authority()) |
|
188 if not auth.realm(): |
|
189 info = self.trUtf8("<b>Enter username and password for '{0}'</b>")\ |
|
190 .format(urlRoot) |
|
191 else: |
|
192 info = self.trUtf8("<b>Enter username and password for '{0}', " |
|
193 "realm '{1}'</b>").format(urlRoot, auth.realm()) |
|
194 |
|
195 dlg = AuthenticationDialog(info, auth.user(), |
|
196 Preferences.getHelp("SavePasswords"), |
|
197 Preferences.getHelp("SavePasswords")) |
|
198 if Preferences.getHelp("SavePasswords"): |
|
199 username, password = \ |
|
200 Helpviewer.HelpWindow.HelpWindow.passwordManager().getLogin( |
|
201 reply.url(), auth.realm()) |
|
202 if username: |
|
203 dlg.setData(username, password) |
|
204 if dlg.exec_() == QDialog.Accepted: |
|
205 username, password = dlg.getData() |
|
206 auth.setUser(username) |
|
207 auth.setPassword(password) |
|
208 if Preferences.getHelp("SavePasswords"): |
|
209 Helpviewer.HelpWindow.HelpWindow.passwordManager().setLogin( |
|
210 reply.url(), auth.realm(), username, password) |
|
211 |
|
212 def __proxyAuthenticationRequired(self, proxy, auth): |
|
213 """ |
|
214 Private slot to handle a proxy authentication request. |
|
215 |
|
216 @param proxy reference to the proxy object (QNetworkProxy) |
|
217 @param auth reference to the authenticator object (QAuthenticator) |
|
218 """ |
|
219 info = self.trUtf8("<b>Connect to proxy '{0}' using:</b>")\ |
|
220 .format(Qt.escape(proxy.hostName())) |
|
221 |
|
222 dlg = AuthenticationDialog(info, proxy.user(), True) |
|
223 if dlg.exec_() == QDialog.Accepted: |
|
224 username, password = dlg.getData() |
|
225 auth.setUser(username) |
|
226 auth.setPassword(password) |
|
227 if dlg.shallSave(): |
|
228 Preferences.setUI("ProxyUser", username) |
|
229 Preferences.setUI("ProxyPassword", password) |
|
230 self.__proxy.setUser(username) |
|
231 self.__proxy.setPassword(password) |
|
232 |
|
233 def __sslErrors(self, reply, errors): |
|
234 """ |
|
235 Private slot to handle SSL errors. |
|
236 |
|
237 @param reply reference to the reply object (QNetworkReply) |
|
238 @param errors list of SSL errors (list of QSslError) |
|
239 """ |
|
240 caMerge = QSslCertificate.fromData(Preferences.Prefs.settings\ |
|
241 .value("Help/CaCertificates").toByteArray()) |
|
242 caNew = [] |
|
243 |
|
244 errorStrings = [] |
|
245 for err in errors: |
|
246 if err.certificate() in caMerge: |
|
247 continue |
|
248 errorStrings.append(err.errorString()) |
|
249 if not err.certificate().isNull(): |
|
250 caNew.append(err.certificate()) |
|
251 if not errorStrings: |
|
252 reply.ignoreSslErrors() |
|
253 return |
|
254 |
|
255 errorString = '.</li><li>'.join(errorStrings) |
|
256 ret = QMessageBox.warning(None, |
|
257 self.trUtf8("SSL Errors"), |
|
258 self.trUtf8("""<p>SSL Errors for <br /><b>{0}</b>""" |
|
259 """<ul><li>{1}</li></ul></p>""" |
|
260 """<p>Do you want to ignore these errors?</p>""")\ |
|
261 .format(reply.url().toString(), errorString), |
|
262 QMessageBox.StandardButtons( |
|
263 QMessageBox.No | \ |
|
264 QMessageBox.Yes), |
|
265 QMessageBox.No) |
|
266 |
|
267 if ret == QMessageBox.Yes: |
|
268 if len(caNew) > 0: |
|
269 certinfos = [] |
|
270 for cert in caNew: |
|
271 certinfos.append(self.__certToString(cert)) |
|
272 ret = QMessageBox.question(None, |
|
273 self.trUtf8("Certificates"), |
|
274 self.trUtf8("""<p>Certificates:<br/>{0}<br/>""" |
|
275 """Do you want to accept all these certificates?</p>""")\ |
|
276 .format("".join(certinfos)), |
|
277 QMessageBox.StandardButtons(\ |
|
278 QMessageBox.No | \ |
|
279 QMessageBox.Yes), |
|
280 QMessageBox.No) |
|
281 if ret == QMessageBox.Yes: |
|
282 for cert in caNew: |
|
283 caMerge.append(cert) |
|
284 |
|
285 sslCfg = QSslConfiguration.defaultConfiguration() |
|
286 caList = sslCfg.caCertificates() |
|
287 for cert in caNew: |
|
288 caList.append(cert) |
|
289 sslCfg.setCaCertificates(caList) |
|
290 QSslConfiguration.setDefaultConfiguration(sslCfg) |
|
291 reply.setSslConfiguration(sslCfg) |
|
292 |
|
293 pems = QByteArray() |
|
294 for cert in caMerge: |
|
295 pems.append(cert.toPem() + '\n') |
|
296 Preferences.Prefs.settings.setValue("Help/CaCertificates", |
|
297 QVariant(pems)) |
|
298 |
|
299 reply.ignoreSslErrors() |
|
300 |
|
301 def __certToString(self, cert): |
|
302 """ |
|
303 Private method to convert a certificate to a formatted string. |
|
304 |
|
305 @param cert certificate to convert (QSslCertificate) |
|
306 @return formatted string (string) |
|
307 """ |
|
308 result = "<p>" |
|
309 |
|
310 result += cert.subjectInfo(QSslCertificate.CommonName) |
|
311 |
|
312 result += self.trUtf8("<br/>Issuer: {0}")\ |
|
313 .format(cert.issuerInfo(QSslCertificate.CommonName)) |
|
314 |
|
315 result += self.trUtf8("<br/>Not valid before: {0}<br/>Valid Until: {1}")\ |
|
316 .format(cert.effectiveDate().toString(Qt.ISODate), |
|
317 cert.expiryDate().toString(Qt.ISODate)) |
|
318 |
|
319 names = cert.alternateSubjectNames() |
|
320 tmpList = names.get(QSsl.DnsEntry, []) |
|
321 if tmpList: |
|
322 result += self.trUtf8("<br/>Alternate Names:<ul><li>{0}</li></ul>")\ |
|
323 .format("</li><li>".join(tmpList)) |
|
324 |
|
325 result += "</p>" |
|
326 |
|
327 return result |
|
328 |
|
329 def preferencesChanged(self): |
|
330 """ |
|
331 Public slot to signal a change of preferences. |
|
332 """ |
|
333 self.__setAccessManagerProxy() |
|
334 self.__setDiskCache() |
|
335 |
|
336 def languagesChanged(self): |
|
337 """ |
|
338 Public slot to (re-)load the list of accepted languages. |
|
339 """ |
|
340 languages = Preferences.Prefs.settings.value( |
|
341 "Help/AcceptLanguages", |
|
342 QVariant(HelpLanguagesDialog.defaultAcceptLanguages()))\ |
|
343 .toStringList() |
|
344 self.__acceptLanguage = HelpLanguagesDialog.httpString(languages) |
|
345 |
|
346 def __setDiskCache(self): |
|
347 """ |
|
348 Private method to set the disk cache. |
|
349 """ |
|
350 if NetworkDiskCache is not None: |
|
351 if Preferences.getHelp("DiskCacheEnabled"): |
|
352 diskCache = NetworkDiskCache(self) |
|
353 location = os.path.join(Utilities.getConfigDir(), "browser", 'cache') |
|
354 size = Preferences.getHelp("DiskCacheSize") * 1024 * 1024 |
|
355 diskCache.setCacheDirectory(location) |
|
356 diskCache.setMaximumCacheSize(size) |
|
357 else: |
|
358 diskCache = None |
|
359 self.setCache(diskCache) |