diff --git a/resources/lib/services/nfsession/nfsession_ops.py b/resources/lib/services/nfsession/nfsession_ops.py index 2d6115e9ca..07b9becba0 100644 --- a/resources/lib/services/nfsession/nfsession_ops.py +++ b/resources/lib/services/nfsession/nfsession_ops.py @@ -128,7 +128,7 @@ def activate_profile(self, guid): G.LOCAL_DB.switch_active_profile(guid) G.CACHE_MANAGEMENT.identifier_prefix = guid - cookies.save(self.session.cookies) + cookies.save(self.session.cookies.jar) def parental_control_data(self, guid, password): # Ask to the service if password is right and get the PIN status diff --git a/resources/lib/services/nfsession/session/access.py b/resources/lib/services/nfsession/session/access.py index f68757e618..297b4fc846 100644 --- a/resources/lib/services/nfsession/session/access.py +++ b/resources/lib/services/nfsession/session/access.py @@ -9,6 +9,7 @@ See LICENSES/MIT.md for more information. """ import re +from http.cookiejar import Cookie import resources.lib.utils.website as website import resources.lib.common as common @@ -82,8 +83,29 @@ def login_auth_data(self, data=None, password=None): # Add the cookies to the session self.session.cookies.clear() for cookie in data['cookies']: - self.session.cookies.set(cookie[0], cookie[1], **cookie[2]) - cookies.log_cookie(self.session.cookies) + # The code below has been adapted from httpx.Cookies.set() method + kwargs = { + 'version': 0, + 'name': cookie['name'], + 'value': cookie['value'], + 'port': None, + 'port_specified': False, + 'domain': cookie['domain'], + 'domain_specified': bool(cookie['domain']), + 'domain_initial_dot': cookie['domain'].startswith('.'), + 'path': cookie['path'], + 'path_specified': bool(cookie['path']), + 'secure': cookie['secure'], + 'expires': cookie.get('expires'), + 'discard': True, + 'comment': None, + 'comment_url': None, + 'rest': cookie.get('rest', {'HttpOnly': None}), + 'rfc2109': False, + } + cookie = Cookie(**kwargs) + self.session.cookies.jar.set_cookie(cookie) + cookies.log_cookie(self.session.cookies.jar) # Try access to website try: website.extract_session_data(self.get('browse'), validate=True, update_profiles=True) @@ -113,7 +135,7 @@ def login_auth_data(self, data=None, password=None): common.set_credentials({'email': email, 'password': password}) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) - cookies.save(self.session.cookies) + cookies.save(self.session.cookies.jar) return True @measure_exec_time_decorator(is_immediate=True) @@ -136,7 +158,7 @@ def login(self, credentials=None): common.set_credentials(credentials) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) - cookies.save(self.session.cookies) + cookies.save(self.session.cookies.jar) return True except LoginValidateError as exc: self.session.cookies.clear() diff --git a/resources/lib/services/nfsession/session/cookie.py b/resources/lib/services/nfsession/session/cookie.py index 92a1cdb26f..ff852b3cac 100644 --- a/resources/lib/services/nfsession/session/cookie.py +++ b/resources/lib/services/nfsession/session/cookie.py @@ -46,8 +46,7 @@ def _verify_session_cookies(self): LOG.error('The cookie "{}" do not exist, it is not possible to check the expiration', cookie_name) return False - for cookie in self.session.cookies.items(): - # TODO to check if 'cookie' name comparison works and also expires property + for cookie in self.session.cookies.jar: if cookie != cookie_name: continue if cookie.expires <= int(time.time()): diff --git a/resources/lib/services/nfsession/session/http_requests.py b/resources/lib/services/nfsession/session/http_requests.py index 426537a3e9..43e91ddd5f 100644 --- a/resources/lib/services/nfsession/session/http_requests.py +++ b/resources/lib/services/nfsession/session/http_requests.py @@ -104,7 +104,7 @@ def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" try: self.auth_url = website.extract_session_data(self.get('browse'))['auth_url'] - cookies.save(self.session.cookies) + cookies.save(self.session.cookies.jar) LOG.debug('Successfully refreshed session data') return True except MbrStatusError: diff --git a/resources/lib/utils/cookies.py b/resources/lib/utils/cookies.py index 81e6e0aa2f..fd0b36167a 100644 --- a/resources/lib/utils/cookies.py +++ b/resources/lib/utils/cookies.py @@ -8,6 +8,8 @@ See LICENSES/MIT.md for more information. """ import pickle +from http.cookiejar import CookieJar +from threading import RLock from time import time import xbmcvfs @@ -17,14 +19,38 @@ from resources.lib.utils.logging import LOG +class PickleableCookieJar(CookieJar): + """A pickleable CookieJar class""" + # This code has been adapted from RequestsCookieJar of "Requests" module + @classmethod + def cast(cls, cookie_jar: CookieJar): + """Make a kind of cast to convert the class from CookieJar to PickleableCookieJar""" + assert isinstance(cookie_jar, CookieJar) + cookie_jar.__class__ = cls + assert isinstance(cookie_jar, PickleableCookieJar) + return cookie_jar + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop('_cookies_lock') + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if '_cookies_lock' not in self.__dict__: + self._cookies_lock = RLock() + + def save(cookie_jar, log_output=True): """Save a cookie jar to file and in-memory storage""" if log_output: log_cookie(cookie_jar) cookie_file = xbmcvfs.File(cookie_file_path(), 'wb') try: - # pickle.dump(cookie_jar, cookie_file) - cookie_file.write(bytearray(pickle.dumps(cookie_jar))) + cookie_file.write(bytearray(pickle.dumps(PickleableCookieJar.cast(cookie_jar)))) except Exception as exc: # pylint: disable=broad-except LOG.error('Failed to save cookies to file: {exc}', exc=exc) finally: @@ -81,11 +107,15 @@ def cookie_file_path(): def convert_chrome_cookie(cookie): """Convert a cookie from Chrome to a CookieJar format type""" - kwargs = {'domain': cookie['domain']} + kwargs = { + 'name': cookie['name'], + 'value': cookie['value'], + 'domain': cookie['domain'], + 'path': cookie['path'], + 'secure': cookie['secure'] + } if cookie['expires'] != -1: kwargs['expires'] = int(cookie['expires']) - kwargs['path'] = cookie['path'] - kwargs['secure'] = cookie['secure'] if cookie['httpOnly']: kwargs['rest'] = {'HttpOnly': True} - return cookie['name'], cookie['value'], kwargs + return kwargs