src/eric7/WebBrowser/AdBlock/AdBlockSubscription.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9328
49a0a9cb2505
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
11 import re 11 import re
12 import hashlib 12 import hashlib
13 import base64 13 import base64
14 14
15 from PyQt6.QtCore import ( 15 from PyQt6.QtCore import (
16 pyqtSignal, Qt, QObject, QByteArray, QDateTime, QUrl, QUrlQuery, 16 pyqtSignal,
17 QCryptographicHash, QDate, QTime 17 Qt,
18 QObject,
19 QByteArray,
20 QDateTime,
21 QUrl,
22 QUrlQuery,
23 QCryptographicHash,
24 QDate,
25 QTime,
18 ) 26 )
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest 27 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
20 28
21 from EricWidgets import EricMessageBox 29 from EricWidgets import EricMessageBox
22 30
25 33
26 34
27 class AdBlockSubscription(QObject): 35 class AdBlockSubscription(QObject):
28 """ 36 """
29 Class implementing the AdBlock subscription. 37 Class implementing the AdBlock subscription.
30 38
31 @signal changed() emitted after the subscription has changed 39 @signal changed() emitted after the subscription has changed
32 @signal rulesChanged() emitted after the subscription's rules have changed 40 @signal rulesChanged() emitted after the subscription's rules have changed
33 @signal enabledChanged(bool) emitted after the enabled state was changed 41 @signal enabledChanged(bool) emitted after the enabled state was changed
34 @signal rulesEnabledChanged() emitted after a rule enabled state was 42 @signal rulesEnabledChanged() emitted after a rule enabled state was
35 changed 43 changed
36 """ 44 """
45
37 changed = pyqtSignal() 46 changed = pyqtSignal()
38 rulesChanged = pyqtSignal() 47 rulesChanged = pyqtSignal()
39 enabledChanged = pyqtSignal(bool) 48 enabledChanged = pyqtSignal(bool)
40 rulesEnabledChanged = pyqtSignal() 49 rulesEnabledChanged = pyqtSignal()
41 50
42 def __init__(self, url, custom, parent=None, default=False): 51 def __init__(self, url, custom, parent=None, default=False):
43 """ 52 """
44 Constructor 53 Constructor
45 54
46 @param url AdBlock URL for the subscription (QUrl) 55 @param url AdBlock URL for the subscription (QUrl)
47 @param custom flag indicating a custom subscription (boolean) 56 @param custom flag indicating a custom subscription (boolean)
48 @param parent reference to the parent object (QObject) 57 @param parent reference to the parent object (QObject)
49 @param default flag indicating a default subscription (boolean) 58 @param default flag indicating a default subscription (boolean)
50 """ 59 """
51 super().__init__(parent) 60 super().__init__(parent)
52 61
53 self.__custom = custom 62 self.__custom = custom
54 self.__url = url.toEncoded() 63 self.__url = url.toEncoded()
55 self.__enabled = False 64 self.__enabled = False
56 self.__downloading = None 65 self.__downloading = None
57 self.__defaultSubscription = default 66 self.__defaultSubscription = default
58 67
59 self.__title = "" 68 self.__title = ""
60 self.__location = QByteArray() 69 self.__location = QByteArray()
61 self.__lastUpdate = QDateTime() 70 self.__lastUpdate = QDateTime()
62 self.__requiresLocation = "" 71 self.__requiresLocation = ""
63 self.__requiresTitle = "" 72 self.__requiresTitle = ""
64 73
65 self.__updatePeriod = 0 # update period in hours, 0 = use default 74 self.__updatePeriod = 0 # update period in hours, 0 = use default
66 self.__remoteModified = QDateTime() 75 self.__remoteModified = QDateTime()
67 76
68 self.__rules = [] # list containing all AdBlock rules 77 self.__rules = [] # list containing all AdBlock rules
69 78
70 self.__checksumRe = re.compile( 79 self.__checksumRe = re.compile(
71 r"""^\s*!\s*checksum[\s\-:]+([\w\+\/=]+).*\n""", 80 r"""^\s*!\s*checksum[\s\-:]+([\w\+\/=]+).*\n""",
72 re.IGNORECASE | re.MULTILINE) 81 re.IGNORECASE | re.MULTILINE,
82 )
73 self.__expiresRe = re.compile( 83 self.__expiresRe = re.compile(
74 r"""(?:expires:|expires after)\s*(\d+)\s*(hour|h)?""", 84 r"""(?:expires:|expires after)\s*(\d+)\s*(hour|h)?""", re.IGNORECASE
75 re.IGNORECASE) 85 )
76 self.__remoteModifiedRe = re.compile( 86 self.__remoteModifiedRe = re.compile(
77 r"""!\s*(?:Last modified|Updated):\s*(\d{1,2})\s*""" 87 r"""!\s*(?:Last modified|Updated):\s*(\d{1,2})\s*"""
78 r"""(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*""" 88 r"""(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*"""
79 r"""(\d{2,4})\s*((\d{1,2}):(\d{2}))?""", 89 r"""(\d{2,4})\s*((\d{1,2}):(\d{2}))?""",
80 re.IGNORECASE) 90 re.IGNORECASE,
81 91 )
92
82 self.__monthNameToNumber = { 93 self.__monthNameToNumber = {
83 "Jan": 1, 94 "Jan": 1,
84 "Feb": 2, 95 "Feb": 2,
85 "Mar": 3, 96 "Mar": 3,
86 "Apr": 4, 97 "Apr": 4,
89 "Jul": 7, 100 "Jul": 7,
90 "Aug": 8, 101 "Aug": 8,
91 "Sep": 9, 102 "Sep": 9,
92 "Oct": 10, 103 "Oct": 10,
93 "Nov": 11, 104 "Nov": 11,
94 "Dec": 12 105 "Dec": 12,
95 } 106 }
96 107
97 self.__parseUrl(url) 108 self.__parseUrl(url)
98 109
99 def __parseUrl(self, url): 110 def __parseUrl(self, url):
100 """ 111 """
101 Private method to parse the AdBlock URL for the subscription. 112 Private method to parse the AdBlock URL for the subscription.
102 113
103 @param url AdBlock URL for the subscription 114 @param url AdBlock URL for the subscription
104 @type QUrl 115 @type QUrl
105 """ 116 """
106 if url.scheme() != "abp": 117 if url.scheme() != "abp":
107 return 118 return
108 119
109 if url.path() != "subscribe": 120 if url.path() != "subscribe":
110 return 121 return
111 122
112 urlQuery = QUrlQuery(url) 123 urlQuery = QUrlQuery(url)
113 self.__title = QUrl.fromPercentEncoding( 124 self.__title = QUrl.fromPercentEncoding(
114 QByteArray(urlQuery.queryItemValue("title").encode())) 125 QByteArray(urlQuery.queryItemValue("title").encode())
126 )
115 self.__enabled = urlQuery.queryItemValue("enabled") != "false" 127 self.__enabled = urlQuery.queryItemValue("enabled") != "false"
116 self.__location = QByteArray(QUrl.fromPercentEncoding( 128 self.__location = QByteArray(
117 QByteArray(urlQuery.queryItemValue("location").encode())) 129 QUrl.fromPercentEncoding(
118 .encode("utf-8")) 130 QByteArray(urlQuery.queryItemValue("location").encode())
119 131 ).encode("utf-8")
132 )
133
120 # Check for required subscription 134 # Check for required subscription
121 self.__requiresLocation = QUrl.fromPercentEncoding( 135 self.__requiresLocation = QUrl.fromPercentEncoding(
122 QByteArray(urlQuery.queryItemValue( 136 QByteArray(urlQuery.queryItemValue("requiresLocation").encode())
123 "requiresLocation").encode())) 137 )
124 self.__requiresTitle = QUrl.fromPercentEncoding( 138 self.__requiresTitle = QUrl.fromPercentEncoding(
125 QByteArray(urlQuery.queryItemValue("requiresTitle").encode())) 139 QByteArray(urlQuery.queryItemValue("requiresTitle").encode())
140 )
126 if self.__requiresLocation and self.__requiresTitle: 141 if self.__requiresLocation and self.__requiresTitle:
127 from WebBrowser.WebBrowserWindow import WebBrowserWindow 142 from WebBrowser.WebBrowserWindow import WebBrowserWindow
143
128 WebBrowserWindow.adBlockManager().loadRequiredSubscription( 144 WebBrowserWindow.adBlockManager().loadRequiredSubscription(
129 self.__requiresLocation, self.__requiresTitle) 145 self.__requiresLocation, self.__requiresTitle
130 146 )
147
131 lastUpdateString = urlQuery.queryItemValue("lastUpdate") 148 lastUpdateString = urlQuery.queryItemValue("lastUpdate")
132 self.__lastUpdate = QDateTime.fromString(lastUpdateString, 149 self.__lastUpdate = QDateTime.fromString(
133 Qt.DateFormat.ISODate) 150 lastUpdateString, Qt.DateFormat.ISODate
134 151 )
152
135 self.__loadRules() 153 self.__loadRules()
136 154
137 def url(self): 155 def url(self):
138 """ 156 """
139 Public method to generate the URL for this subscription. 157 Public method to generate the URL for this subscription.
140 158
141 @return AdBlock URL for the subscription 159 @return AdBlock URL for the subscription
142 @rtype QUrl 160 @rtype QUrl
143 """ 161 """
144 url = QUrl() 162 url = QUrl()
145 url.setScheme("abp") 163 url.setScheme("abp")
146 url.setPath("subscribe") 164 url.setPath("subscribe")
147 165
148 queryItems = [] 166 queryItems = []
149 queryItems.append(("location", bytes(self.__location).decode())) 167 queryItems.append(("location", bytes(self.__location).decode()))
150 queryItems.append(("title", self.__title)) 168 queryItems.append(("title", self.__title))
151 if self.__requiresLocation and self.__requiresTitle: 169 if self.__requiresLocation and self.__requiresTitle:
152 queryItems.append(("requiresLocation", self.__requiresLocation)) 170 queryItems.append(("requiresLocation", self.__requiresLocation))
153 queryItems.append(("requiresTitle", self.__requiresTitle)) 171 queryItems.append(("requiresTitle", self.__requiresTitle))
154 if not self.__enabled: 172 if not self.__enabled:
155 queryItems.append(("enabled", "false")) 173 queryItems.append(("enabled", "false"))
156 if self.__lastUpdate.isValid(): 174 if self.__lastUpdate.isValid():
157 queryItems.append( 175 queryItems.append(
158 ("lastUpdate", 176 ("lastUpdate", self.__lastUpdate.toString(Qt.DateFormat.ISODate))
159 self.__lastUpdate.toString(Qt.DateFormat.ISODate))
160 ) 177 )
161 178
162 query = QUrlQuery() 179 query = QUrlQuery()
163 query.setQueryItems(queryItems) 180 query.setQueryItems(queryItems)
164 url.setQuery(query) 181 url.setQuery(query)
165 return url 182 return url
166 183
167 def isEnabled(self): 184 def isEnabled(self):
168 """ 185 """
169 Public method to check, if the subscription is enabled. 186 Public method to check, if the subscription is enabled.
170 187
171 @return flag indicating the enabled status 188 @return flag indicating the enabled status
172 @rtype bool 189 @rtype bool
173 """ 190 """
174 return self.__enabled 191 return self.__enabled
175 192
176 def setEnabled(self, enabled): 193 def setEnabled(self, enabled):
177 """ 194 """
178 Public method to set the enabled status. 195 Public method to set the enabled status.
179 196
180 @param enabled flag indicating the enabled status 197 @param enabled flag indicating the enabled status
181 @type bool 198 @type bool
182 """ 199 """
183 if self.__enabled == enabled: 200 if self.__enabled == enabled:
184 return 201 return
185 202
186 self.__enabled = enabled 203 self.__enabled = enabled
187 self.enabledChanged.emit(enabled) 204 self.enabledChanged.emit(enabled)
188 205
189 def title(self): 206 def title(self):
190 """ 207 """
191 Public method to get the subscription title. 208 Public method to get the subscription title.
192 209
193 @return subscription title 210 @return subscription title
194 @rtype string 211 @rtype string
195 """ 212 """
196 return self.__title 213 return self.__title
197 214
198 def setTitle(self, title): 215 def setTitle(self, title):
199 """ 216 """
200 Public method to set the subscription title. 217 Public method to set the subscription title.
201 218
202 @param title subscription title 219 @param title subscription title
203 @type str 220 @type str
204 """ 221 """
205 if self.__title == title: 222 if self.__title == title:
206 return 223 return
207 224
208 self.__title = title 225 self.__title = title
209 self.changed.emit() 226 self.changed.emit()
210 227
211 def location(self): 228 def location(self):
212 """ 229 """
213 Public method to get the subscription location. 230 Public method to get the subscription location.
214 231
215 @return URL of the subscription location 232 @return URL of the subscription location
216 @rtype QUrl 233 @rtype QUrl
217 """ 234 """
218 return QUrl.fromEncoded(self.__location) 235 return QUrl.fromEncoded(self.__location)
219 236
220 def setLocation(self, url): 237 def setLocation(self, url):
221 """ 238 """
222 Public method to set the subscription location. 239 Public method to set the subscription location.
223 240
224 @param url URL of the subscription location 241 @param url URL of the subscription location
225 @type QUrl 242 @type QUrl
226 """ 243 """
227 if url == self.location(): 244 if url == self.location():
228 return 245 return
229 246
230 self.__location = url.toEncoded() 247 self.__location = url.toEncoded()
231 self.__lastUpdate = QDateTime() 248 self.__lastUpdate = QDateTime()
232 self.changed.emit() 249 self.changed.emit()
233 250
234 def requiresLocation(self): 251 def requiresLocation(self):
235 """ 252 """
236 Public method to get the location of a required subscription. 253 Public method to get the location of a required subscription.
237 254
238 @return location of a required subscription 255 @return location of a required subscription
239 @rtype str 256 @rtype str
240 """ 257 """
241 return self.__requiresLocation 258 return self.__requiresLocation
242 259
243 def lastUpdate(self): 260 def lastUpdate(self):
244 """ 261 """
245 Public method to get the date and time of the last update. 262 Public method to get the date and time of the last update.
246 263
247 @return date and time of the last update 264 @return date and time of the last update
248 @rtype QDateTime 265 @rtype QDateTime
249 """ 266 """
250 return self.__lastUpdate 267 return self.__lastUpdate
251 268
252 def rulesFileName(self): 269 def rulesFileName(self):
253 """ 270 """
254 Public method to get the name of the rules file. 271 Public method to get the name of the rules file.
255 272
256 @return name of the rules file 273 @return name of the rules file
257 @rtype str 274 @rtype str
258 """ 275 """
259 if self.location().scheme() == "file": 276 if self.location().scheme() == "file":
260 return self.location().toLocalFile() 277 return self.location().toLocalFile()
261 278
262 if self.__location.isEmpty(): 279 if self.__location.isEmpty():
263 return "" 280 return ""
264 281
265 sha1 = bytes(QCryptographicHash.hash( 282 sha1 = bytes(
266 self.__location, QCryptographicHash.Algorithm.Sha1).toHex() 283 QCryptographicHash.hash(
284 self.__location, QCryptographicHash.Algorithm.Sha1
285 ).toHex()
267 ).decode() 286 ).decode()
268 dataDir = os.path.join( 287 dataDir = os.path.join(Utilities.getConfigDir(), "web_browser", "subscriptions")
269 Utilities.getConfigDir(), "web_browser", "subscriptions")
270 if not os.path.exists(dataDir): 288 if not os.path.exists(dataDir):
271 os.makedirs(dataDir) 289 os.makedirs(dataDir)
272 fileName = os.path.join( 290 fileName = os.path.join(dataDir, "adblock_subscription_{0}".format(sha1))
273 dataDir, "adblock_subscription_{0}".format(sha1))
274 return fileName 291 return fileName
275 292
276 def __loadRules(self): 293 def __loadRules(self):
277 """ 294 """
278 Private method to load the rules of the subscription. 295 Private method to load the rules of the subscription.
279 """ 296 """
280 fileName = self.rulesFileName() 297 fileName = self.rulesFileName()
284 header = f.readline().strip() 301 header = f.readline().strip()
285 if not header.startswith("[Adblock"): 302 if not header.startswith("[Adblock"):
286 EricMessageBox.warning( 303 EricMessageBox.warning(
287 None, 304 None,
288 self.tr("Load subscription rules"), 305 self.tr("Load subscription rules"),
289 self.tr("""AdBlock file '{0}' does not start""" 306 self.tr(
290 """ with [Adblock.""") 307 """AdBlock file '{0}' does not start"""
291 .format(fileName)) 308 """ with [Adblock."""
309 ).format(fileName),
310 )
292 f.close() 311 f.close()
293 os.unlink(fileName) 312 os.unlink(fileName)
294 self.__lastUpdate = QDateTime() 313 self.__lastUpdate = QDateTime()
295 else: 314 else:
296 from .AdBlockRule import AdBlockRule 315 from .AdBlockRule import AdBlockRule
297 316
298 self.__updatePeriod = 0 317 self.__updatePeriod = 0
299 self.__remoteModified = QDateTime() 318 self.__remoteModified = QDateTime()
300 self.__rules = [] 319 self.__rules = []
301 self.__rules.append(AdBlockRule(header, self)) 320 self.__rules.append(AdBlockRule(header, self))
302 for line in f.readlines(): 321 for line in f.readlines():
311 # hours 330 # hours
312 self.__updatePeriod = int(period) 331 self.__updatePeriod = int(period)
313 else: 332 else:
314 # days 333 # days
315 self.__updatePeriod = int(period) * 24 334 self.__updatePeriod = int(period) * 24
316 remoteModified = self.__remoteModifiedRe.search( 335 remoteModified = self.__remoteModifiedRe.search(line)
317 line)
318 if remoteModified: 336 if remoteModified:
319 day, month, year, time, hour, minute = ( 337 (
320 remoteModified.groups() 338 day,
321 ) 339 month,
340 year,
341 time,
342 hour,
343 minute,
344 ) = remoteModified.groups()
322 self.__remoteModified.setDate( 345 self.__remoteModified.setDate(
323 QDate(int(year), 346 QDate(
324 self.__monthNameToNumber[month], 347 int(year),
325 int(day)) 348 self.__monthNameToNumber[month],
349 int(day),
350 )
326 ) 351 )
327 if time: 352 if time:
328 self.__remoteModified.setTime( 353 self.__remoteModified.setTime(
329 QTime(int(hour), int(minute))) 354 QTime(int(hour), int(minute))
355 )
330 else: 356 else:
331 # no time given, set it to 23:59 357 # no time given, set it to 23:59
332 self.__remoteModified.setTime( 358 self.__remoteModified.setTime(QTime(23, 59))
333 QTime(23, 59))
334 self.changed.emit() 359 self.changed.emit()
335 except OSError as err: 360 except OSError as err:
336 EricMessageBox.warning( 361 EricMessageBox.warning(
337 None, 362 None,
338 self.tr("Load subscription rules"), 363 self.tr("Load subscription rules"),
339 self.tr( 364 self.tr(
340 """Unable to read AdBlock file '{0}'.\nReason: {1}""") 365 """Unable to read AdBlock file '{0}'.\nReason: {1}"""
341 .format(fileName, str(err)) 366 ).format(fileName, str(err)),
342 ) 367 )
343 368
344 elif not fileName.endswith("_custom"): 369 elif not fileName.endswith("_custom"):
345 self.__lastUpdate = QDateTime() 370 self.__lastUpdate = QDateTime()
346 371
347 self.checkForUpdate() 372 self.checkForUpdate()
348 373
349 def checkForUpdate(self): 374 def checkForUpdate(self):
350 """ 375 """
351 Public method to check for an update. 376 Public method to check for an update.
352 """ 377 """
353 updatePeriod = ( 378 updatePeriod = (
354 self.__updatePeriod 379 self.__updatePeriod
355 if self.__updatePeriod else 380 if self.__updatePeriod
356 Preferences.getWebBrowser("AdBlockUpdatePeriod") * 24 381 else Preferences.getWebBrowser("AdBlockUpdatePeriod") * 24
357 ) 382 )
358 if ( 383 if (
359 not self.__lastUpdate.isValid() or 384 not self.__lastUpdate.isValid()
360 (self.__remoteModified.isValid() and 385 or (
361 self.__remoteModified.addSecs(updatePeriod * 3600) < 386 self.__remoteModified.isValid()
362 QDateTime.currentDateTime()) or 387 and self.__remoteModified.addSecs(updatePeriod * 3600)
363 self.__lastUpdate.addSecs(updatePeriod * 3600) < 388 < QDateTime.currentDateTime()
364 QDateTime.currentDateTime() 389 )
390 or self.__lastUpdate.addSecs(updatePeriod * 3600)
391 < QDateTime.currentDateTime()
365 ): 392 ):
366 self.updateNow() 393 self.updateNow()
367 394
368 def updateNow(self): 395 def updateNow(self):
369 """ 396 """
370 Public method to update the subscription immediately. 397 Public method to update the subscription immediately.
371 """ 398 """
372 if self.__downloading is not None: 399 if self.__downloading is not None:
373 return 400 return
374 401
375 if not self.location().isValid(): 402 if not self.location().isValid():
376 return 403 return
377 404
378 if self.location().scheme() == "file": 405 if self.location().scheme() == "file":
379 self.__lastUpdate = QDateTime.currentDateTime() 406 self.__lastUpdate = QDateTime.currentDateTime()
380 self.__loadRules() 407 self.__loadRules()
381 return 408 return
382 409
383 from WebBrowser.WebBrowserWindow import WebBrowserWindow 410 from WebBrowser.WebBrowserWindow import WebBrowserWindow
384 reply = WebBrowserWindow.networkManager().get( 411
385 QNetworkRequest(self.location())) 412 reply = WebBrowserWindow.networkManager().get(QNetworkRequest(self.location()))
386 reply.finished.connect( 413 reply.finished.connect(lambda: self.__rulesDownloaded(reply))
387 lambda: self.__rulesDownloaded(reply))
388 self.__downloading = reply 414 self.__downloading = reply
389 415
390 def __rulesDownloaded(self, reply): 416 def __rulesDownloaded(self, reply):
391 """ 417 """
392 Private slot to deal with the downloaded rules. 418 Private slot to deal with the downloaded rules.
393 419
394 @param reply reference to the network reply 420 @param reply reference to the network reply
395 @type QNetworkReply 421 @type QNetworkReply
396 """ 422 """
397 response = bytes(reply.readAll()) 423 response = bytes(reply.readAll())
398 reply.close() 424 reply.close()
399 self.__downloading = None 425 self.__downloading = None
400 426
401 if reply.error() != QNetworkReply.NetworkError.NoError: 427 if reply.error() != QNetworkReply.NetworkError.NoError:
402 if not self.__defaultSubscription: 428 if not self.__defaultSubscription:
403 # don't show error if we try to load the default 429 # don't show error if we try to load the default
404 EricMessageBox.warning( 430 EricMessageBox.warning(
405 None, 431 None,
406 self.tr("Downloading subscription rules"), 432 self.tr("Downloading subscription rules"),
407 self.tr( 433 self.tr(
408 """<p>Subscription rules could not be""" 434 """<p>Subscription rules could not be"""
409 """ downloaded.</p><p>Error: {0}</p>""") 435 """ downloaded.</p><p>Error: {0}</p>"""
410 .format(reply.errorString())) 436 ).format(reply.errorString()),
437 )
411 else: 438 else:
412 # reset after first download attempt 439 # reset after first download attempt
413 self.__defaultSubscription = False 440 self.__defaultSubscription = False
414 return 441 return
415 442
416 if not response: 443 if not response:
417 EricMessageBox.warning( 444 EricMessageBox.warning(
418 None, 445 None,
419 self.tr("Downloading subscription rules"), 446 self.tr("Downloading subscription rules"),
420 self.tr("""Got empty subscription rules.""")) 447 self.tr("""Got empty subscription rules."""),
421 return 448 )
422 449 return
450
423 fileName = self.rulesFileName() 451 fileName = self.rulesFileName()
424 try: 452 try:
425 with open(fileName, "wb") as f: 453 with open(fileName, "wb") as f:
426 from WebBrowser.WebBrowserWindow import WebBrowserWindow 454 from WebBrowser.WebBrowserWindow import WebBrowserWindow
455
427 if ( 456 if (
428 WebBrowserWindow.adBlockManager().useLimitedEasyList() and 457 WebBrowserWindow.adBlockManager().useLimitedEasyList()
429 self.url().toString().startswith( 458 and self.url()
430 WebBrowserWindow.adBlockManager() 459 .toString()
431 .getDefaultSubscriptionUrl()) 460 .startswith(
461 WebBrowserWindow.adBlockManager().getDefaultSubscriptionUrl()
462 )
432 ): 463 ):
433 limited = True 464 limited = True
434 # ignore Third-party advertisers rules for performance 465 # ignore Third-party advertisers rules for performance
435 # whitelist rules at the end will be used 466 # whitelist rules at the end will be used
436 index = response.find( 467 index = response.find(
437 b"!---------------------------" 468 b"!---------------------------"
438 b"Third-party advertisers" 469 b"Third-party advertisers"
439 b"---------------------------!") 470 b"---------------------------!"
471 )
440 part1 = response[:index] 472 part1 = response[:index]
441 index = response.find( 473 index = response.find(
442 b"!-----------------------" 474 b"!-----------------------"
443 b"Whitelists to fix broken sites" 475 b"Whitelists to fix broken sites"
444 b"------------------------!") 476 b"------------------------!"
477 )
445 part2 = response[index:] 478 part2 = response[index:]
446 f.write(part1) 479 f.write(part1)
447 f.write(part2) 480 f.write(part2)
448 else: 481 else:
449 limited = False 482 limited = False
450 f.write(response) 483 f.write(response)
451 f.close() 484 f.close()
452 self.__lastUpdate = QDateTime.currentDateTime() 485 self.__lastUpdate = QDateTime.currentDateTime()
453 486
454 if limited or self.__validateCheckSum(fileName): 487 if limited or self.__validateCheckSum(fileName):
455 self.__loadRules() 488 self.__loadRules()
456 else: 489 else:
457 os.unlink(fileName) 490 os.unlink(fileName)
458 except OSError: 491 except OSError:
459 EricMessageBox.warning( 492 EricMessageBox.warning(
460 None, 493 None,
461 self.tr("Downloading subscription rules"), 494 self.tr("Downloading subscription rules"),
462 self.tr("""Unable to write to AdBlock file '{0}'.""") 495 self.tr("""Unable to write to AdBlock file '{0}'.""").file(fileName),
463 .file(fileName)) 496 )
464 self.__downloading = None 497 self.__downloading = None
465 reply.deleteLater() 498 reply.deleteLater()
466 499
467 def __validateCheckSum(self, fileName): 500 def __validateCheckSum(self, fileName):
468 """ 501 """
469 Private method to check the subscription file's checksum. 502 Private method to check the subscription file's checksum.
470 503
471 @param fileName name of the file containing the subscription 504 @param fileName name of the file containing the subscription
472 @type str 505 @type str
473 @return flag indicating a valid file. A file is considered 506 @return flag indicating a valid file. A file is considered
474 valid, if the checksum is OK, the file does not contain a 507 valid, if the checksum is OK, the file does not contain a
475 checksum (i.e. cannot be checked) or we are using the limited 508 checksum (i.e. cannot be checked) or we are using the limited
479 try: 512 try:
480 with open(fileName, "r", encoding="utf-8") as f: 513 with open(fileName, "r", encoding="utf-8") as f:
481 data = f.read() 514 data = f.read()
482 except (OSError, OSError): 515 except (OSError, OSError):
483 return False 516 return False
484 517
485 match = re.search(self.__checksumRe, data) 518 match = re.search(self.__checksumRe, data)
486 if match: 519 if match:
487 expectedChecksum = match.group(1) 520 expectedChecksum = match.group(1)
488 else: 521 else:
489 # consider it as valid 522 # consider it as valid
490 return True 523 return True
491 524
492 # normalize the data 525 # normalize the data
493 data = re.sub(r"\r", "", data) # normalize eol 526 data = re.sub(r"\r", "", data) # normalize eol
494 data = re.sub(r"\n+", "\n", data) # remove empty lines 527 data = re.sub(r"\n+", "\n", data) # remove empty lines
495 data = re.sub(self.__checksumRe, "", data) # remove checksum line 528 data = re.sub(self.__checksumRe, "", data) # remove checksum line
496 529
497 # calculate checksum 530 # calculate checksum
498 md5 = hashlib.md5() # secok 531 md5 = hashlib.md5() # secok
499 md5.update(data.encode("utf-8")) 532 md5.update(data.encode("utf-8"))
500 calculatedChecksum = ( 533 calculatedChecksum = base64.b64encode(md5.digest()).decode().rstrip("=")
501 base64.b64encode(md5.digest()).decode().rstrip("=")
502 )
503 if calculatedChecksum == expectedChecksum: 534 if calculatedChecksum == expectedChecksum:
504 return True 535 return True
505 else: 536 else:
506 res = EricMessageBox.yesNo( 537 res = EricMessageBox.yesNo(
507 None, 538 None,
509 self.tr( 540 self.tr(
510 """<p>AdBlock subscription <b>{0}</b> has a wrong""" 541 """<p>AdBlock subscription <b>{0}</b> has a wrong"""
511 """ checksum.<br/>""" 542 """ checksum.<br/>"""
512 """Found: {1}<br/>""" 543 """Found: {1}<br/>"""
513 """Calculated: {2}<br/>""" 544 """Calculated: {2}<br/>"""
514 """Use it anyway?</p>""") 545 """Use it anyway?</p>"""
515 .format(self.__title, expectedChecksum, 546 ).format(self.__title, expectedChecksum, calculatedChecksum),
516 calculatedChecksum)) 547 )
517 return res 548 return res
518 549
519 def saveRules(self): 550 def saveRules(self):
520 """ 551 """
521 Public method to save the subscription rules. 552 Public method to save the subscription rules.
522 """ 553 """
523 fileName = self.rulesFileName() 554 fileName = self.rulesFileName()
524 if not fileName: 555 if not fileName:
525 return 556 return
526 557
527 try: 558 try:
528 with open(fileName, "w", encoding="utf-8") as f: 559 with open(fileName, "w", encoding="utf-8") as f:
529 if not self.__rules or not self.__rules[0].isHeader(): 560 if not self.__rules or not self.__rules[0].isHeader():
530 f.write("[Adblock Plus 2.0]\n") 561 f.write("[Adblock Plus 2.0]\n")
531 for rule in self.__rules: 562 for rule in self.__rules:
532 f.write(rule.filter() + "\n") 563 f.write(rule.filter() + "\n")
533 except OSError: 564 except OSError:
534 EricMessageBox.warning( 565 EricMessageBox.warning(
535 None, 566 None,
536 self.tr("Saving subscription rules"), 567 self.tr("Saving subscription rules"),
537 self.tr("""Unable to write to AdBlock file '{0}'.""") 568 self.tr("""Unable to write to AdBlock file '{0}'.""").format(fileName),
538 .format(fileName)) 569 )
539 570
540 def rule(self, offset): 571 def rule(self, offset):
541 """ 572 """
542 Public method to get a specific rule. 573 Public method to get a specific rule.
543 574
544 @param offset offset of the rule 575 @param offset offset of the rule
545 @type int 576 @type int
546 @return requested rule 577 @return requested rule
547 @rtype AdBlockRule 578 @rtype AdBlockRule
548 """ 579 """
549 if offset >= len(self.__rules): 580 if offset >= len(self.__rules):
550 return None 581 return None
551 582
552 return self.__rules[offset] 583 return self.__rules[offset]
553 584
554 def allRules(self): 585 def allRules(self):
555 """ 586 """
556 Public method to get the list of rules. 587 Public method to get the list of rules.
557 588
558 @return list of rules 589 @return list of rules
559 @rtype list of AdBlockRule 590 @rtype list of AdBlockRule
560 """ 591 """
561 return self.__rules[:] 592 return self.__rules[:]
562 593
563 def addRule(self, rule): 594 def addRule(self, rule):
564 """ 595 """
565 Public method to add a rule. 596 Public method to add a rule.
566 597
567 @param rule reference to the rule to add 598 @param rule reference to the rule to add
568 @type AdBlockRule 599 @type AdBlockRule
569 @return offset of the rule 600 @return offset of the rule
570 @rtype int 601 @rtype int
571 """ 602 """
572 self.__rules.append(rule) 603 self.__rules.append(rule)
573 self.rulesChanged.emit() 604 self.rulesChanged.emit()
574 605
575 return len(self.__rules) - 1 606 return len(self.__rules) - 1
576 607
577 def removeRule(self, offset): 608 def removeRule(self, offset):
578 """ 609 """
579 Public method to remove a rule given the offset. 610 Public method to remove a rule given the offset.
580 611
581 @param offset offset of the rule to remove 612 @param offset offset of the rule to remove
582 @type int 613 @type int
583 """ 614 """
584 if offset < 0 or offset > len(self.__rules): 615 if offset < 0 or offset > len(self.__rules):
585 return 616 return
586 617
587 del self.__rules[offset] 618 del self.__rules[offset]
588 self.rulesChanged.emit() 619 self.rulesChanged.emit()
589 620
590 def replaceRule(self, rule, offset): 621 def replaceRule(self, rule, offset):
591 """ 622 """
592 Public method to replace a rule given the offset. 623 Public method to replace a rule given the offset.
593 624
594 @param rule reference to the rule to set 625 @param rule reference to the rule to set
595 @type AdBlockRule 626 @type AdBlockRule
596 @param offset offset of the rule to remove 627 @param offset offset of the rule to remove
597 @type int 628 @type int
598 @return requested rule 629 @return requested rule
599 @rtype AdBlockRule 630 @rtype AdBlockRule
600 """ 631 """
601 if offset >= len(self.__rules): 632 if offset >= len(self.__rules):
602 return None 633 return None
603 634
604 self.__rules[offset] = rule 635 self.__rules[offset] = rule
605 self.rulesChanged.emit() 636 self.rulesChanged.emit()
606 637
607 return self.__rules[offset] 638 return self.__rules[offset]
608 639
609 def canEditRules(self): 640 def canEditRules(self):
610 """ 641 """
611 Public method to check, if rules can be edited. 642 Public method to check, if rules can be edited.
612 643
613 @return flag indicating rules may be edited 644 @return flag indicating rules may be edited
614 @rtype bool 645 @rtype bool
615 """ 646 """
616 return self.__custom 647 return self.__custom
617 648
618 def canBeRemoved(self): 649 def canBeRemoved(self):
619 """ 650 """
620 Public method to check, if the subscription can be removed. 651 Public method to check, if the subscription can be removed.
621 652
622 @return flag indicating removal is allowed 653 @return flag indicating removal is allowed
623 @rtype bool 654 @rtype bool
624 """ 655 """
625 return not self.__custom and not self.__defaultSubscription 656 return not self.__custom and not self.__defaultSubscription
626 657
627 def setRuleEnabled(self, offset, enabled): 658 def setRuleEnabled(self, offset, enabled):
628 """ 659 """
629 Public method to enable a specific rule. 660 Public method to enable a specific rule.
630 661
631 @param offset offset of the rule 662 @param offset offset of the rule
632 @type int 663 @type int
633 @param enabled new enabled state 664 @param enabled new enabled state
634 @type bool 665 @type bool
635 @return reference to the changed rule 666 @return reference to the changed rule
636 @rtype AdBlockRule 667 @rtype AdBlockRule
637 """ 668 """
638 if offset >= len(self.__rules): 669 if offset >= len(self.__rules):
639 return None 670 return None
640 671
641 rule = self.__rules[offset] 672 rule = self.__rules[offset]
642 rule.setEnabled(enabled) 673 rule.setEnabled(enabled)
643 self.rulesEnabledChanged.emit() 674 self.rulesEnabledChanged.emit()
644 675
645 if rule.isCSSRule(): 676 if rule.isCSSRule():
646 from WebBrowser.WebBrowserWindow import WebBrowserWindow 677 from WebBrowser.WebBrowserWindow import WebBrowserWindow
678
647 WebBrowserWindow.mainWindow().reloadUserStyleSheet() 679 WebBrowserWindow.mainWindow().reloadUserStyleSheet()
648 680
649 return rule 681 return rule

eric ide

mercurial