--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/WebBrowser/CookieJar/CookieJar.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,501 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a QNetworkCookieJar subclass with various accept policies. +""" + +import os + +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSettings +from PyQt5.QtNetwork import QNetworkCookieJar, QNetworkCookie + +from WebBrowser.WebBrowserWindow import WebBrowserWindow + +from Utilities.AutoSaver import AutoSaver +import Utilities +import Preferences + + +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(Utilities.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