src/eric7/WebBrowser/CookieJar/CookieJar.py

Sun, 18 Dec 2022 19:33:46 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 18 Dec 2022 19:33:46 +0100
branch
eric7
changeset 9624
b47dfa7a137d
parent 9473
3f23dbf37dbe
child 9653
e67609152c5e
permissions
-rw-r--r--

Refactored the Utilities and Globals modules in order to enhance the maintainability.

# -*- coding: utf-8 -*-

# Copyright (c) 2009 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a QNetworkCookieJar subclass with various accept policies.
"""

import os

from PyQt6.QtCore import QSettings, pyqtSignal, pyqtSlot
from PyQt6.QtNetwork import QNetworkCookie, QNetworkCookieJar

from eric7 import Globals, Preferences
from eric7.Utilities.AutoSaver import AutoSaver
from eric7.WebBrowser.WebBrowserWindow import WebBrowserWindow


class CookieJar(QNetworkCookieJar):
    """
    Class implementing a QNetworkCookieJar subclass with various accept
    policies.

    @signal cookiesChanged() emitted after the cookies have been changed
    """

    cookiesChanged = pyqtSignal()

    AcceptAlways = 0
    AcceptNever = 1
    AcceptOnlyFromSitesNavigatedTo = 2
    AcceptMax = 2

    KeepUntilExpire = 0
    KeepUntilExit = 1
    KeepMax = 1

    Allow = 0
    Block = 1
    AllowForSession = 2

    def __init__(self, parent=None):
        """
        Constructor

        @param parent reference to the parent object (QObject)
        """
        super().__init__(parent)

        self.__loaded = False
        self.__acceptCookies = self.AcceptOnlyFromSitesNavigatedTo
        self.__saveTimer = AutoSaver(self, self.__save)

        self.__cookiesFile = os.path.join(
            Globals.getConfigDir(), "web_browser", "cookies.ini"
        )

        self.__store = WebBrowserWindow.webProfile().cookieStore()
        self.__store.setCookieFilter(self.__cookieFilter)
        self.__store.cookieAdded.connect(self.__cookieAdded)
        self.__store.cookieRemoved.connect(self.__cookieRemoved)

        self.__load()
        self.__store.loadAllCookies()

    def close(self):
        """
        Public slot to close the cookie jar.
        """
        if not self.__loaded:
            self.__load()

        if self.__keepCookies == self.KeepUntilExit:
            self.clear()
        self.__saveTimer.saveIfNeccessary()

    def clear(self):
        """
        Public method to clear all cookies.
        """
        if not self.__loaded:
            self.__load()

        self.setAllCookies([])
        self.__store.deleteAllCookies()
        self.cookiesChanged.emit()

    def removeCookies(self, cookies):
        """
        Public method to remove a list of cookies.

        @param cookies list of cookies to be removed
        @type list of QNetworkCookie
        """
        wasBlocked = self.blockSignals(True)
        for cookie in cookies:
            self.__store.deleteCookie(cookie)
        self.blockSignals(wasBlocked)

        self.cookiesChanged.emit()

    def removeCookie(self, cookie):
        """
        Public method to remove a cookie.

        @param cookie cookie to be removed
        @type QNetworkCookie
        """
        self.__store.deleteCookie(cookie)
        self.cookiesChanged.emit()

    def __load(self):
        """
        Private method to load the cookies settings.
        """
        if self.__loaded:
            return

        cookieSettings = QSettings(self.__cookiesFile, QSettings.Format.IniFormat)

        # load exceptions
        self.__exceptionsBlock = Preferences.toList(
            cookieSettings.value("Exceptions/block")
        )
        self.__exceptionsAllow = Preferences.toList(
            cookieSettings.value("Exceptions/allow")
        )
        self.__exceptionsAllowForSession = Preferences.toList(
            cookieSettings.value("Exceptions/allowForSession")
        )
        self.__exceptionsBlock.sort()
        self.__exceptionsAllow.sort()
        self.__exceptionsAllowForSession.sort()

        self.__acceptCookies = Preferences.getWebBrowser("AcceptCookies")
        self.__keepCookies = Preferences.getWebBrowser("KeepCookiesUntil")
        if self.__keepCookies == self.KeepUntilExit:
            self.clear()

        self.__filterTrackingCookies = Preferences.toBool(
            Preferences.getWebBrowser("FilterTrackingCookies")
        )

        self.__loaded = True
        self.cookiesChanged.emit()

    def __save(self):
        """
        Private method to save the cookies settings.
        """
        if not self.__loaded:
            return

        cookieSettings = QSettings(self.__cookiesFile, QSettings.Format.IniFormat)

        cookieSettings.setValue("Exceptions/block", self.__exceptionsBlock)
        cookieSettings.setValue("Exceptions/allow", self.__exceptionsAllow)
        cookieSettings.setValue(
            "Exceptions/allowForSession", self.__exceptionsAllowForSession
        )

        Preferences.setWebBrowser("AcceptCookies", self.__acceptCookies)
        Preferences.setWebBrowser("KeepCookiesUntil", self.__keepCookies)
        Preferences.setWebBrowser("FilterTrackingCookies", self.__filterTrackingCookies)

    @pyqtSlot(QNetworkCookie)
    def __cookieAdded(self, cookie):
        """
        Private slot handling the addition of a cookie.

        @param cookie cookie which was added
        @type QNetworkCookie
        """
        if self.__rejectCookie(cookie, cookie.domain()):
            self.__store.deleteCookie(cookie)
            return

        self.insertCookie(cookie)
        self.cookiesChanged.emit()

    @pyqtSlot(QNetworkCookie)
    def __cookieRemoved(self, cookie):
        """
        Private slot handling the removal of a cookie.

        @param cookie cookie which was removed
        @type QNetworkCookie
        """
        if self.deleteCookie(cookie):
            self.cookiesChanged.emit()

    def __cookieFilter(self, request):
        """
        Private method to filter cookies.

        Note: This method is used for Qt 5.11+ only.

        @param request reference to the cookie filter request object
        @type QWebEngineCookieStore.FilterRequest
        @return flag indicating cookie access is allowed
        @rtype bool
        """
        if not self.__loaded:
            self.__load()

        if self.__acceptCookies == self.AcceptNever:
            res = self.__isOnDomainList(self.__exceptionsAllow, request.origin.host())
            if not res:
                return False

        if self.__acceptCookies == self.AcceptAlways:
            res = self.__isOnDomainList(self.__exceptionsBlock, request.origin.host())
            if res:
                return False

        if (
            self.__acceptCookies == self.AcceptOnlyFromSitesNavigatedTo
            and request.thirdParty
        ):
            return False

        return True

    def __rejectCookie(self, cookie, cookieDomain):
        """
        Private method to test, if a cookie shall be rejected.

        @param cookie cookie to be tested
        @type QNetworkCookie
        @param cookieDomain domain of the cookie
        @type str
        @return flag indicating the cookie shall be rejected
        @rtype bool
        """
        if not self.__loaded:
            self.__load()

        if self.__acceptCookies == self.AcceptNever:
            res = self.__isOnDomainList(self.__exceptionsAllow, cookieDomain)
            if not res:
                return True

        if self.__acceptCookies == self.AcceptAlways:
            res = self.__isOnDomainList(self.__exceptionsBlock, cookieDomain)
            if res:
                return True

        if self.__acceptCookies == self.AcceptOnlyFromSitesNavigatedTo:
            mainWindow = WebBrowserWindow.mainWindow()
            if mainWindow is not None:
                browser = mainWindow.getWindow().currentBrowser()
                if browser is not None:
                    url = browser.url()
                    if url.isValid():
                        host = url.host()
                    else:
                        host = ""
                    res = self.__matchDomain(cookieDomain, host)
                    if not res:
                        return True

        if self.__filterTrackingCookies and cookie.name().startsWith(b"__utm"):
            return True

        return False

    def acceptPolicy(self):
        """
        Public method to get the accept policy.

        @return current accept policy
        """
        if not self.__loaded:
            self.__load()

        return self.__acceptCookies

    def setAcceptPolicy(self, policy):
        """
        Public method to set the accept policy.

        @param policy accept policy to be set
        """
        if not self.__loaded:
            self.__load()

        if policy > self.AcceptMax:
            return
        if policy == self.__acceptCookies:
            return

        self.__acceptCookies = policy
        self.__saveTimer.changeOccurred()

    def keepPolicy(self):
        """
        Public method to get the keep policy.

        @return keep policy
        """
        if not self.__loaded:
            self.__load()

        return self.__keepCookies

    def setKeepPolicy(self, policy):
        """
        Public method to set the keep policy.

        @param policy keep policy to be set
        """
        if not self.__loaded:
            self.__load()

        if policy > self.KeepMax:
            return
        if policy == self.__keepCookies:
            return

        self.__keepCookies = policy
        self.__saveTimer.changeOccurred()

    def blockedCookies(self):
        """
        Public method to return the list of blocked domains.

        @return list of blocked domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        return self.__exceptionsBlock

    def allowedCookies(self):
        """
        Public method to return the list of allowed domains.

        @return list of allowed domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        return self.__exceptionsAllow

    def allowForSessionCookies(self):
        """
        Public method to return the list of allowed session cookie domains.

        @return list of allowed session cookie domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        return self.__exceptionsAllowForSession

    def setBlockedCookies(self, list_):
        """
        Public method to set the list of blocked domains.

        @param list_ list of blocked domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        self.__exceptionsBlock = list_[:]
        self.__exceptionsBlock.sort()
        self.__saveTimer.changeOccurred()

    def setAllowedCookies(self, list_):
        """
        Public method to set the list of allowed domains.

        @param list_ list of allowed domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        self.__exceptionsAllow = list_[:]
        self.__exceptionsAllow.sort()
        self.__saveTimer.changeOccurred()

    def setAllowForSessionCookies(self, list_):
        """
        Public method to set the list of allowed session cookie domains.

        @param list_ list of allowed session cookie domains (list of strings)
        """
        if not self.__loaded:
            self.__load()

        self.__exceptionsAllowForSession = list_[:]
        self.__exceptionsAllowForSession.sort()
        self.__saveTimer.changeOccurred()

    def filterTrackingCookies(self):
        """
        Public method to get the filter tracking cookies flag.

        @return filter tracking cookies flag (boolean)
        """
        return self.__filterTrackingCookies

    def setFilterTrackingCookies(self, filterTrackingCookies):
        """
        Public method to set the filter tracking cookies flag.

        @param filterTrackingCookies filter tracking cookies flag (boolean)
        """
        if filterTrackingCookies == self.__filterTrackingCookies:
            return

        self.__filterTrackingCookies = filterTrackingCookies
        self.__saveTimer.changeOccurred()

    def __isOnDomainList(self, rules, domain):
        """
        Private method to check, if either the rule matches the domain exactly
        or the domain ends with ".rule".

        @param rules list of rules (list of strings)
        @param domain domain name to check (string)
        @return flag indicating a match (boolean)
        """
        for rule in rules:
            if rule.startswith("."):
                if domain.endswith(rule):
                    return True

                withoutDot = rule[1:]
                if domain == withoutDot:
                    return True
            else:
                domainEnding = domain[-(len(rule) + 1) :]
                if domainEnding and domainEnding[0] == "." and domain.endswith(rule):
                    return True

                if rule == domain:
                    return True

        return False

    def __matchDomain(self, cookieDomain, siteDomain):
        """
        Private method to check, if a URLs host matches a cookie domain
        according to RFC 6265.

        @param cookieDomain domain of the cookie
        @type str
        @param siteDomain domain or host of an URL
        @type str
        @return flag indicating a match
        @rtype bool
        """
        if not siteDomain:
            # empty URLs always match
            return True

        if cookieDomain.startswith("."):
            cookieDomain = cookieDomain[1:]
        if siteDomain.startswith("."):
            siteDomain = siteDomain[1:]

        if cookieDomain == siteDomain:
            return True

        if not siteDomain.endswith(cookieDomain):
            return False

        index = siteDomain.find(cookieDomain)
        return index > 0 and siteDomain[index - 1] == "."

    def cookies(self):
        """
        Public method to get the cookies of the cookie jar.

        @return list of all cookies (list of QNetworkCookie)
        """
        if not self.__loaded:
            self.__load()

        return self.allCookies()

    def cookieDomains(self):
        """
        Public method to get a list of all domains used by the cookies.

        @return list of domain names
        @rtype list of str
        """
        domains = []
        for cookie in self.cookies():
            domain = cookie.domain()
            if domain not in domains:
                domains.append(domain)

        return domains

eric ide

mercurial