Sat, 07 Sep 2019 17:35:43 +0200
Closed branch after it was merged into 'default'.
# -*- coding: utf-8 -*- # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a QNetworkCookieJar subclass with various accept policies. """ from __future__ import unicode_literals 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(CookieJar, self).__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() try: self.__store.setCookieFilter(self.__cookieFilter) except AttributeError: # pre Qt 5.11 pass 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.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.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