"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.8/eric/eric6/WebBrowser/AdBlock/AdBlockManager.py" (1 Jan 2020, 22608 Bytes) of package /linux/misc/eric6-20.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "AdBlockManager.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2009 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Module implementing the AdBlock manager.
    8 """
    9 
   10 
   11 import os
   12 
   13 from PyQt5.QtCore import (
   14     pyqtSignal, QObject, QUrl, QUrlQuery, QFile, QByteArray, QMutex,
   15     QMutexLocker
   16 )
   17 from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInfo
   18 
   19 from E5Gui import E5MessageBox
   20 
   21 from .AdBlockSubscription import AdBlockSubscription
   22 from .AdBlockUrlInterceptor import AdBlockUrlInterceptor
   23 from .AdBlockMatcher import AdBlockMatcher
   24 
   25 from Utilities.AutoSaver import AutoSaver
   26 import Utilities
   27 import Preferences
   28 
   29 
   30 class AdBlockManager(QObject):
   31     """
   32     Class implementing the AdBlock manager.
   33     
   34     @signal rulesChanged() emitted after some rule has changed
   35     @signal requiredSubscriptionLoaded(subscription) emitted to indicate
   36         loading of a required subscription is finished (AdBlockSubscription)
   37     @signal enabledChanged(enabled) emitted to indicate a change of the
   38         enabled state
   39     """
   40     rulesChanged = pyqtSignal()
   41     requiredSubscriptionLoaded = pyqtSignal(AdBlockSubscription)
   42     enabledChanged = pyqtSignal(bool)
   43     
   44     def __init__(self, parent=None):
   45         """
   46         Constructor
   47         
   48         @param parent reference to the parent object
   49         @type QObject
   50         """
   51         super(AdBlockManager, self).__init__(parent)
   52         
   53         self.__loaded = False
   54         self.__subscriptionsLoaded = False
   55         self.__enabled = False
   56         self.__adBlockDialog = None
   57         self.__adBlockExceptionsDialog = None
   58         self.__adBlockNetwork = None
   59         self.__adBlockPage = None
   60         self.__subscriptions = []
   61         self.__exceptedHosts = Preferences.getWebBrowser("AdBlockExceptions")
   62         self.__saveTimer = AutoSaver(self, self.save)
   63         self.__limitedEasyList = Preferences.getWebBrowser(
   64             "AdBlockUseLimitedEasyList")
   65         
   66         self.__defaultSubscriptionUrlString = (
   67             "abp:subscribe?location="
   68             "https://easylist-downloads.adblockplus.org/easylist.txt&"
   69             "title=EasyList"
   70         )
   71         self.__additionalDefaultSubscriptionUrlStrings = (
   72             "abp:subscribe?location=https://raw.githubusercontent.com/"
   73             "hoshsadiq/adblock-nocoin-list/master/nocoin.txt&"
   74             "title=NoCoin",
   75         )
   76         self.__customSubscriptionUrlString = (
   77             bytes(self.__customSubscriptionUrl().toEncoded()).decode()
   78         )
   79         
   80         self.__mutex = QMutex()
   81         self.__matcher = AdBlockMatcher(self)
   82         
   83         self.rulesChanged.connect(self.__saveTimer.changeOccurred)
   84         self.rulesChanged.connect(self.__rulesChanged)
   85         
   86         self.__interceptor = AdBlockUrlInterceptor(self)
   87         
   88         from WebBrowser.WebBrowserWindow import WebBrowserWindow
   89         WebBrowserWindow.networkManager().installUrlInterceptor(
   90             self.__interceptor)
   91     
   92     def __rulesChanged(self):
   93         """
   94         Private slot handling a change of the AdBlock rules.
   95         """
   96         from WebBrowser.WebBrowserWindow import WebBrowserWindow
   97         WebBrowserWindow.mainWindow().reloadUserStyleSheet()
   98         self.__updateMatcher()
   99     
  100     def close(self):
  101         """
  102         Public method to close the open search engines manager.
  103         """
  104         self.__adBlockDialog and self.__adBlockDialog.close()
  105         (self.__adBlockExceptionsDialog and
  106          self.__adBlockExceptionsDialog.close())
  107         
  108         self.__saveTimer.saveIfNeccessary()
  109     
  110     def isEnabled(self):
  111         """
  112         Public method to check, if blocking ads is enabled.
  113         
  114         @return flag indicating the enabled state
  115         @rtype bool
  116         """
  117         if not self.__loaded:
  118             self.load()
  119         
  120         return self.__enabled
  121     
  122     def setEnabled(self, enabled):
  123         """
  124         Public slot to set the enabled state.
  125         
  126         @param enabled flag indicating the enabled state
  127         @type bool
  128         """
  129         if self.isEnabled() == enabled:
  130             return
  131         
  132         from WebBrowser.WebBrowserWindow import WebBrowserWindow
  133         self.__enabled = enabled
  134         for mainWindow in WebBrowserWindow.mainWindows():
  135             mainWindow.adBlockIcon().setEnabled(enabled)
  136         if enabled:
  137             self.__loadSubscriptions()
  138         
  139         self.rulesChanged.emit()
  140         self.enabledChanged.emit(enabled)
  141     
  142     def block(self, info):
  143         """
  144         Public method to check, if a request should be blocked.
  145         
  146         @param info request info object
  147         @type QWebEngineUrlRequestInfo
  148         @return flag indicating to block the request
  149         @rtype bool
  150         """
  151         locker = QMutexLocker(self.__mutex)     # __IGNORE_WARNING__
  152         
  153         if not self.isEnabled():
  154             return False
  155         
  156         urlString = bytes(info.requestUrl().toEncoded()).decode().lower()
  157         urlDomain = info.requestUrl().host().lower()
  158         urlScheme = info.requestUrl().scheme().lower()
  159         
  160         if (
  161             not self.canRunOnScheme(urlScheme) or
  162             not self.__canBeBlocked(info.firstPartyUrl())
  163         ):
  164             return False
  165         
  166         res = False
  167         blockedRule = self.__matcher.match(info, urlDomain, urlString)
  168         
  169         if blockedRule:
  170             res = True
  171             if (
  172                 info.resourceType() ==
  173                     QWebEngineUrlRequestInfo.ResourceTypeMainFrame
  174             ):
  175                 url = QUrl("eric:adblock")
  176                 query = QUrlQuery()
  177                 query.addQueryItem("rule", blockedRule.filter())
  178                 query.addQueryItem(
  179                     "subscription", blockedRule.subscription().title())
  180                 url.setQuery(query)
  181                 info.redirect(url)
  182             else:
  183                 info.block(True)
  184         
  185         return res
  186     
  187     def canRunOnScheme(self, scheme):
  188         """
  189         Public method to check, if AdBlock can be performed on the scheme.
  190         
  191         @param scheme scheme to check
  192         @type str
  193         @return flag indicating, that AdBlock can be performed
  194         @rtype bool
  195         """
  196         return scheme not in ["data", "eric", "qthelp", "qrc", "file", "abp"]
  197     
  198     def page(self):
  199         """
  200         Public method to get a reference to the page block object.
  201         
  202         @return reference to the page block object
  203         @rtype AdBlockPage
  204         """
  205         if self.__adBlockPage is None:
  206             from .AdBlockPage import AdBlockPage
  207             self.__adBlockPage = AdBlockPage(self)
  208         return self.__adBlockPage
  209     
  210     def __customSubscriptionLocation(self):
  211         """
  212         Private method to generate the path for custom subscriptions.
  213         
  214         @return URL for custom subscriptions
  215         @rtype QUrl
  216         """
  217         dataDir = os.path.join(Utilities.getConfigDir(), "web_browser",
  218                                "subscriptions")
  219         if not os.path.exists(dataDir):
  220             os.makedirs(dataDir)
  221         fileName = os.path.join(dataDir, "adblock_subscription_custom")
  222         return QUrl.fromLocalFile(fileName)
  223     
  224     def __customSubscriptionUrl(self):
  225         """
  226         Private method to generate the URL for custom subscriptions.
  227         
  228         @return URL for custom subscriptions
  229         @rtype QUrl
  230         """
  231         location = self.__customSubscriptionLocation()
  232         encodedUrl = bytes(location.toEncoded()).decode()
  233         url = QUrl("abp:subscribe?location={0}&title={1}".format(
  234             encodedUrl, self.tr("Custom Rules")))
  235         return url
  236     
  237     def customRules(self):
  238         """
  239         Public method to get a subscription for custom rules.
  240         
  241         @return subscription object for custom rules
  242         @rtype AdBlockSubscription
  243         """
  244         location = self.__customSubscriptionLocation()
  245         for subscription in self.__subscriptions:
  246             if subscription.location() == location:
  247                 return subscription
  248         
  249         url = self.__customSubscriptionUrl()
  250         customAdBlockSubscription = AdBlockSubscription(url, True, self)
  251         self.addSubscription(customAdBlockSubscription)
  252         return customAdBlockSubscription
  253     
  254     def subscriptions(self):
  255         """
  256         Public method to get all subscriptions.
  257         
  258         @return list of subscriptions
  259         @rtype list of AdBlockSubscription
  260         """
  261         if not self.__loaded:
  262             self.load()
  263         
  264         return self.__subscriptions[:]
  265     
  266     def subscription(self, location):
  267         """
  268         Public method to get a subscription based on its location.
  269         
  270         @param location location of the subscription to search for
  271         @type str
  272         @return subscription or None
  273         @rtype AdBlockSubscription
  274         """
  275         if location != "":
  276             for subscription in self.__subscriptions:
  277                 if subscription.location().toString() == location:
  278                     return subscription
  279         
  280         return None
  281     
  282     def updateAllSubscriptions(self):
  283         """
  284         Public method to update all subscriptions.
  285         """
  286         for subscription in self.__subscriptions:
  287             subscription.updateNow()
  288     
  289     def removeSubscription(self, subscription, emitSignal=True):
  290         """
  291         Public method to remove an AdBlock subscription.
  292         
  293         @param subscription AdBlock subscription to be removed
  294         @type AdBlockSubscription
  295         @param emitSignal flag indicating to send a signal
  296         @type bool
  297         """
  298         if subscription is None:
  299             return
  300         
  301         if subscription.url().toString().startswith(
  302             (self.__defaultSubscriptionUrlString,
  303              self.__customSubscriptionUrlString)):
  304             return
  305         
  306         try:
  307             self.__subscriptions.remove(subscription)
  308             rulesFileName = subscription.rulesFileName()
  309             QFile.remove(rulesFileName)
  310             requiresSubscriptions = self.getRequiresSubscriptions(subscription)
  311             for requiresSubscription in requiresSubscriptions:
  312                 self.removeSubscription(requiresSubscription, False)
  313             if emitSignal:
  314                 self.rulesChanged.emit()
  315         except ValueError:
  316             pass
  317     
  318     def addSubscriptionFromUrl(self, url):
  319         """
  320         Public method to ad an AdBlock subscription given the abp URL.
  321         
  322         @param url URL to subscribe an AdBlock subscription
  323         @type QUrl
  324         @return flag indicating success
  325         @rtype bool
  326         """
  327         if url.path() != "subscribe":
  328             return False
  329         
  330         title = QUrl.fromPercentEncoding(
  331             QByteArray(QUrlQuery(url).queryItemValue("title").encode()))
  332         if not title:
  333             return False
  334         
  335         res = E5MessageBox.yesNo(
  336             None,
  337             self.tr("Subscribe?"),
  338             self.tr(
  339                 """<p>Subscribe to this AdBlock subscription?</p>"""
  340                 """<p>{0}</p>""").format(title))
  341         if res:
  342             from .AdBlockSubscription import AdBlockSubscription
  343             from WebBrowser.WebBrowserWindow import WebBrowserWindow
  344             
  345             dlg = WebBrowserWindow.adBlockManager().showDialog()
  346             subscription = AdBlockSubscription(
  347                 url, False,
  348                 WebBrowserWindow.adBlockManager())
  349             WebBrowserWindow.adBlockManager().addSubscription(subscription)
  350             dlg.addSubscription(subscription, False)
  351             dlg.setFocus()
  352             dlg.raise_()
  353         
  354         return res
  355     
  356     def addSubscription(self, subscription):
  357         """
  358         Public method to add an AdBlock subscription.
  359         
  360         @param subscription AdBlock subscription to be added
  361         @type AdBlockSubscription
  362         """
  363         if subscription is None:
  364             return
  365         
  366         self.__subscriptions.insert(-1, subscription)
  367         
  368         subscription.rulesChanged.connect(self.rulesChanged)
  369         subscription.changed.connect(self.rulesChanged)
  370         subscription.enabledChanged.connect(self.rulesChanged)
  371         
  372         self.rulesChanged.emit()
  373     
  374     def save(self):
  375         """
  376         Public method to save the AdBlock subscriptions.
  377         """
  378         if not self.__loaded:
  379             return
  380         
  381         Preferences.setWebBrowser("AdBlockEnabled", self.__enabled)
  382         if self.__subscriptionsLoaded:
  383             subscriptions = []
  384             requiresSubscriptions = []
  385             # intermediate store for subscription requiring others
  386             for subscription in self.__subscriptions:
  387                 if subscription is None:
  388                     continue
  389                 urlString = bytes(subscription.url().toEncoded()).decode()
  390                 if "requiresLocation" in urlString:
  391                     requiresSubscriptions.append(urlString)
  392                 else:
  393                     subscriptions.append(urlString)
  394                 subscription.saveRules()
  395             for subscription in requiresSubscriptions:
  396                 subscriptions.insert(-1, subscription)  # custom should be last
  397             Preferences.setWebBrowser("AdBlockSubscriptions", subscriptions)
  398     
  399     def load(self):
  400         """
  401         Public method to load the AdBlock subscriptions.
  402         """
  403         if self.__loaded:
  404             return
  405         
  406         self.__loaded = True
  407         
  408         self.__enabled = Preferences.getWebBrowser("AdBlockEnabled")
  409         if self.__enabled:
  410             self.__loadSubscriptions()
  411     
  412     def __loadSubscriptions(self):
  413         """
  414         Private method to load the set of subscriptions.
  415         """
  416         if self.__subscriptionsLoaded:
  417             return
  418         
  419         subscriptions = Preferences.getWebBrowser("AdBlockSubscriptions")
  420         if subscriptions:
  421             for subscription in subscriptions:
  422                 if subscription.startswith(self.__customSubscriptionUrlString):
  423                     break
  424             else:
  425                 subscriptions.append(self.__customSubscriptionUrlString)
  426         else:
  427             subscriptions = (
  428                 [self.__defaultSubscriptionUrlString] +
  429                 self.__additionalDefaultSubscriptionUrlStrings +
  430                 [self.__customSubscriptionUrlString]
  431             )
  432         for subscription in subscriptions:
  433             url = QUrl.fromEncoded(subscription.encode("utf-8"))
  434             adBlockSubscription = AdBlockSubscription(
  435                 url,
  436                 subscription.startswith(self.__customSubscriptionUrlString),
  437                 self,
  438                 subscription.startswith(self.__defaultSubscriptionUrlString))
  439             adBlockSubscription.rulesChanged.connect(self.rulesChanged)
  440             adBlockSubscription.changed.connect(self.rulesChanged)
  441             adBlockSubscription.enabledChanged.connect(self.rulesChanged)
  442             adBlockSubscription.rulesEnabledChanged.connect(
  443                 self.__updateMatcher)
  444             adBlockSubscription.rulesEnabledChanged.connect(
  445                 self.__saveTimer.changeOccurred)
  446             self.__subscriptions.append(adBlockSubscription)
  447         
  448         self.__subscriptionsLoaded = True
  449         
  450         self.__updateMatcher()
  451     
  452     def loadRequiredSubscription(self, location, title):
  453         """
  454         Public method to load a subscription required by another one.
  455         
  456         @param location location of the required subscription
  457         @type str
  458         @param title title of the required subscription
  459         @type str
  460         """
  461         # Step 1: check, if the subscription is in the list of subscriptions
  462         urlString = "abp:subscribe?location={0}&title={1}".format(
  463             location, title)
  464         for subscription in self.__subscriptions:
  465             if subscription.url().toString().startswith(urlString):
  466                 # We found it!
  467                 return
  468         
  469         # Step 2: if it is not, get it
  470         url = QUrl.fromEncoded(urlString.encode("utf-8"))
  471         adBlockSubscription = AdBlockSubscription(url, False, self)
  472         self.addSubscription(adBlockSubscription)
  473         self.requiredSubscriptionLoaded.emit(adBlockSubscription)
  474     
  475     def getRequiresSubscriptions(self, subscription):
  476         """
  477         Public method to get a list of subscriptions, that require the given
  478         one.
  479         
  480         @param subscription subscription to check for
  481         @type AdBlockSubscription
  482         @return list of subscription requiring the given one
  483         @rtype list of AdBlockSubscription
  484         """
  485         subscriptions = []
  486         location = subscription.location().toString()
  487         for subscription in self.__subscriptions:
  488             if subscription.requiresLocation() == location:
  489                 subscriptions.append(subscription)
  490         
  491         return subscriptions
  492     
  493     def showDialog(self):
  494         """
  495         Public slot to show the AdBlock subscription management dialog.
  496         
  497         @return reference to the dialog
  498         @rtype AdBlockDialog
  499         """
  500         if self.__adBlockDialog is None:
  501             from .AdBlockDialog import AdBlockDialog
  502             self.__adBlockDialog = AdBlockDialog(self)
  503         
  504         self.__adBlockDialog.show()
  505         return self.__adBlockDialog
  506     
  507     def elementHidingRules(self, url):
  508         """
  509         Public method to get the element hiding rules.
  510         
  511         
  512         @param url URL to get hiding rules for
  513         @type QUrl
  514         @return element hiding rules
  515         @rtype str
  516         """
  517         if (
  518             not self.isEnabled() or
  519             not self.canRunOnScheme(url.scheme()) or
  520             not self.__canBeBlocked(url)
  521         ):
  522             return ""
  523         
  524         return self.__matcher.elementHidingRules()
  525     
  526     def elementHidingRulesForDomain(self, url):
  527         """
  528         Public method to get the element hiding rules for a domain.
  529         
  530         @param url URL to get hiding rules for
  531         @type QUrl
  532         @return element hiding rules
  533         @rtype str
  534         """
  535         if (
  536             not self.isEnabled() or
  537             not self.canRunOnScheme(url.scheme()) or
  538             not self.__canBeBlocked(url)
  539         ):
  540             return ""
  541         
  542         return self.__matcher.elementHidingRulesForDomain(url.host())
  543     
  544     def exceptions(self):
  545         """
  546         Public method to get a list of excepted hosts.
  547         
  548         @return list of excepted hosts
  549         @rtype list of str
  550         """
  551         return self.__exceptedHosts
  552     
  553     def setExceptions(self, hosts):
  554         """
  555         Public method to set the list of excepted hosts.
  556         
  557         @param hosts list of excepted hosts
  558         @type list of str
  559         """
  560         self.__exceptedHosts = [host.lower() for host in hosts]
  561         Preferences.setWebBrowser("AdBlockExceptions", self.__exceptedHosts)
  562     
  563     def addException(self, host):
  564         """
  565         Public method to add an exception.
  566         
  567         @param host to be excepted
  568         @type str
  569         """
  570         host = host.lower()
  571         if host and host not in self.__exceptedHosts:
  572             self.__exceptedHosts.append(host)
  573             Preferences.setWebBrowser(
  574                 "AdBlockExceptions", self.__exceptedHosts)
  575     
  576     def removeException(self, host):
  577         """
  578         Public method to remove an exception.
  579         
  580         @param host to be removed from the list of exceptions
  581         @type str
  582         """
  583         host = host.lower()
  584         if host in self.__exceptedHosts:
  585             self.__exceptedHosts.remove(host)
  586             Preferences.setWebBrowser(
  587                 "AdBlockExceptions", self.__exceptedHosts)
  588     
  589     def isHostExcepted(self, host):
  590         """
  591         Public slot to check, if a host is excepted.
  592         
  593         @param host host to check
  594         @type str
  595         @return flag indicating an exception
  596         @rtype bool
  597         """
  598         host = host.lower()
  599         return host in self.__exceptedHosts
  600     
  601     def showExceptionsDialog(self):
  602         """
  603         Public method to show the AdBlock Exceptions dialog.
  604         
  605         @return reference to the exceptions dialog
  606         @rtype AdBlockExceptionsDialog
  607         """
  608         if self.__adBlockExceptionsDialog is None:
  609             from .AdBlockExceptionsDialog import AdBlockExceptionsDialog
  610             self.__adBlockExceptionsDialog = AdBlockExceptionsDialog()
  611         
  612         self.__adBlockExceptionsDialog.load(self.__exceptedHosts)
  613         self.__adBlockExceptionsDialog.show()
  614         return self.__adBlockExceptionsDialog
  615     
  616     def useLimitedEasyList(self):
  617         """
  618         Public method to test, if limited EasyList rules shall be used.
  619         
  620         @return flag indicating limited EasyList rules
  621         @rtype bool
  622         """
  623         return self.__limitedEasyList
  624     
  625     def setUseLimitedEasyList(self, limited):
  626         """
  627         Public method to set the limited EasyList flag.
  628         
  629         @param limited flag indicating to use limited EasyList
  630         @type bool
  631         """
  632         self.__limitedEasyList = limited
  633         
  634         for subscription in self.__subscriptions:
  635             if subscription.url().toString().startswith(
  636                     self.__defaultSubscriptionUrlString):
  637                 subscription.updateNow()
  638         
  639         Preferences.setWebBrowser("AdBlockUseLimitedEasyList", limited)
  640     
  641     def getDefaultSubscriptionUrl(self):
  642         """
  643         Public method to get the default subscription URL.
  644         
  645         @return default subscription URL
  646         @rtype str
  647         """
  648         return self.__defaultSubscriptionUrlString
  649     
  650     def __updateMatcher(self):
  651         """
  652         Private slot to update the adblock matcher.
  653         """
  654         from WebBrowser.WebBrowserWindow import WebBrowserWindow
  655         WebBrowserWindow.networkManager().removeUrlInterceptor(
  656             self.__interceptor)
  657         
  658         if self.__enabled:
  659             self.__matcher.update()
  660         else:
  661             self.__matcher.clear()
  662         
  663         WebBrowserWindow.networkManager().installUrlInterceptor(
  664             self.__interceptor)
  665     
  666     def __canBeBlocked(self, url):
  667         """
  668         Private method to check, if the given URL could be blocked (i.e. is
  669         not whitelisted).
  670         
  671         @param url URL to be checked
  672         @type QUrl
  673         @return flag indicating that the given URL can be blocked
  674         @rtype bool
  675         """
  676         return not self.__matcher.adBlockDisabledForUrl(url)