From d8a22bbb316a9bd48337cacbdad72ee4e0f502d0 Mon Sep 17 00:00:00 2001 From: Lorenzo Di Fuccia Date: Thu, 30 Apr 2020 19:02:43 +0200 Subject: [PATCH] Fix #210 Update HTTP Headers Add check for subscription expiration Add a script to register to ORLY --- register_user.py | 153 +++++++++++++++++++++++++++++++++++++++++++++++ safaribooks.py | 18 +++--- 2 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 register_user.py diff --git a/register_user.py b/register_user.py new file mode 100644 index 0000000..93f3fc6 --- /dev/null +++ b/register_user.py @@ -0,0 +1,153 @@ +import re +import requests +import safaribooks + +REGISTER_URL = safaribooks.SAFARI_BASE_URL + "/register/" +CHECK_EMAIL = safaribooks.SAFARI_BASE_URL + "/check-email-availability/" +CHECK_PWD = safaribooks.SAFARI_BASE_URL + "/check-password/" + +# DEBUG +USE_PROXY = False +PROXIES = {"https": "https://127.0.0.1:8080"} + +CSRF_TOKEN_RE = re.compile(r"(?<=name='csrfmiddlewaretoken' value=')([^']+)") + + +class Register: + def __init__(self, email, password, first_name, second_name, country="US", referrer="podcast"): + self.email = email + self.password = password + self.first_name = first_name + self.second_name = second_name + self.country = country + self.referrer = referrer + + self.csrf = None + + self.session = requests.Session() + if USE_PROXY: # DEBUG + self.session.proxies = PROXIES + self.session.verify = False + + self.session.headers.update(safaribooks.SafariBooks.HEADERS) + self.session.headers.update({ + "X-Requested-With": "XMLHttpRequest", + "Referer": REGISTER_URL + }) + + self.register() + + def handle_cookie_update(self, set_cookie_headers): + for morsel in set_cookie_headers: + # Handle Float 'max-age' Cookie + if safaribooks.SafariBooks.COOKIE_FLOAT_MAX_AGE_PATTERN.search(morsel): + cookie_key, cookie_value = morsel.split(";")[0].split("=") + self.session.cookies.set(cookie_key, cookie_value) + + def requests_provider(self, url, is_post=False, data=None, perform_redirect=True, check_200=True, **kwargs): + try: + response = getattr(self.session, "post" if is_post else "get")( + url, + data=data, + allow_redirects=False, + **kwargs + ) + + self.handle_cookie_update(response.raw.headers.getlist("Set-Cookie")) + + except (requests.ConnectionError, requests.ConnectTimeout, requests.RequestException) as request_exception: + print("Error: ", str(request_exception)) + return 0 + + if response.is_redirect and perform_redirect: + return self.requests_provider(response.next.url, is_post, None, perform_redirect, check_200, **kwargs) + + if check_200 and response.status_code != 200: + print("Invalid response code:\n", response.text) + return 0 + + return response + + def register(self): + # Take first cookie + csrf + response = self.requests_provider(REGISTER_URL) + if response == 0: + print("Error 0x1: unable to reach registration page!") + exit(1) + + if "csrfmiddlewaretoken' value='" not in response.text: + print("Error 0x2: CSRF token not present") + exit(1) + + csrf_search = CSRF_TOKEN_RE.findall(response.text) + if not len(csrf_search): + print("Error 0x3: CSRF token RE error") + exit(1) + + self.csrf = csrf_search[0] + + # Check user validity + response = self.requests_provider(CHECK_EMAIL, params={"email": self.email}) + if response == 0: + print("Error 0x4: unable to check email!") + exit(1) + + response_dict = response.json() + if not response_dict["success"]: + print("Error 0x5:", response_dict["message"]) + exit(1) + + # Check password validity + response = self.requests_provider(CHECK_PWD, is_post=True, data={ + "csrfmiddlewaretoken": self.csrf, + "password1": self.password, + "field_name": "password1" + }) + if response == 0: + print("Error 0x6: unable to check password!") + exit(1) + + response_dict = response.json() + if not response_dict["valid"]: + print("Error 0x7:", response_dict["msg"]) + exit(1) + + # Register + response = self.requests_provider(REGISTER_URL, is_post=True, data={ + "next": "", + "trial_length": 10, + "csrfmiddlewaretoken": self.csrf, + "first_name": self.first_name, + "last_name": self.second_name, + "email": self.email, + "password1": self.password, + "country": self.country, + "referrer": "podcast", + "recently_viewed_bits": "[]" + }, check_200=False) + if response == 0: + print("Error 0x8: unable to register!") + exit(1) + + elif response.status_code != 201: + print("Error: 0x9: invalid status code while registering!") + exit(1) + + print("[*] Account registered: \nEMAIL: %s\nPASSWORD: %s" % (self.email, self.password)) + return + + +if __name__ == "__main__": + import sys + if len(sys.argv) < 3: + print("[!] Error: too few arguments.\nRun `register_user.py EMAIL PASSWORD`.") + exit(1) + + elif len(sys.argv) > 3: + print("[!] Error: too much arguments, try to enclose the string with quote '\"'.") + exit(1) + + FIRST_NAME = "Safari" + SECOND_NAME = "Download" + + Register(sys.argv[1], sys.argv[2], FIRST_NAME, SECOND_NAME) diff --git a/safaribooks.py b/safaribooks.py index 6e3a894..c29dc40 100755 --- a/safaribooks.py +++ b/safaribooks.py @@ -299,13 +299,12 @@ class SafariBooks: "" HEADERS = { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "accept-encoding": "gzip, deflate", - "origin": SAFARI_BASE_URL, - "referer": LOGIN_ENTRY_URL, - "upgrade-insecure-requests": "1", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/60.0.3112.113 Safari/537.36" + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate", + "Referer": LOGIN_ENTRY_URL, + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/80.0.3987.163 Safari/537.36" } COOKIE_FLOAT_MAX_AGE_PATTERN = re.compile(r'(max-age=\d*\.\d*)', re.IGNORECASE) @@ -522,9 +521,12 @@ class SafariBooks: if response == 0: self.display.exit("Login: unable to reach Safari Books Online. Try again...") - if response.status_code != 200: + elif response.status_code != 200: self.display.exit("Authentication issue: unable to access profile page.") + elif "user_type\":\"Expired" in response.text: + self.display.exit("Authentication issue: account subscription expired.") + self.display.info("Successfully authenticated.", state=True) def get_book_info(self):