mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 04:55:07 +00:00
fix(api): parse client cookies with the dl cookie parser
This commit is contained in:
@@ -29,6 +29,14 @@ def test_load_client_cookies_reads_a_transport_blob():
|
||||
assert [(c.name, c.value) for c in jar] == [("session-id", "123-456")]
|
||||
|
||||
|
||||
def test_load_client_cookies_reads_what_local_dl_reads():
|
||||
"""A file with no Netscape header, space-separated rows and an HttpOnly prefix loads as it does locally."""
|
||||
text = "#HttpOnly_.example.com TRUE / TRUE 2000000000 session-id 123-456\n"
|
||||
jar = load_client_cookies(transport_blob(text))
|
||||
assert jar is not None
|
||||
assert [(c.name, c.value) for c in jar] == [("session-id", "123-456")]
|
||||
|
||||
|
||||
def test_load_client_cookies_without_a_blob():
|
||||
assert load_client_cookies(None) is None
|
||||
assert load_client_cookies("") is None
|
||||
|
||||
@@ -5627,11 +5627,18 @@ class dl:
|
||||
@staticmethod
|
||||
def load_cookie_file(cookie_file: Path) -> MozillaCookieJar:
|
||||
"""Load a Netscape cookie file with every expiry blanked, so no layer drops an expired cookie."""
|
||||
cookie_jar = MozillaCookieJar(cookie_file)
|
||||
cookie_data = html.unescape(cookie_file.read_text("utf-8-sig")).splitlines(keepends=False)
|
||||
cookie_jar = dl.parse_cookie_text(cookie_file.read_text("utf-8-sig"), str(cookie_file))
|
||||
cookie_jar.filename = str(cookie_file)
|
||||
return cookie_jar
|
||||
|
||||
@staticmethod
|
||||
def parse_cookie_text(text: str, source: str) -> MozillaCookieJar:
|
||||
"""Parse Netscape cookie text the way :meth:`load_cookie_file` does; ``source`` names it in errors."""
|
||||
cookie_jar = MozillaCookieJar()
|
||||
cookie_data = html.unescape(text).splitlines(keepends=False)
|
||||
head = next((line.lstrip() for line in cookie_data if line.strip()), "")
|
||||
if head[:1] in ("[", "{"):
|
||||
raise ValueError(f"{cookie_file} is a JSON export; export it again in Netscape format")
|
||||
raise ValueError(f"{source} is a JSON export; export it again in Netscape format")
|
||||
for i, line in enumerate(cookie_data):
|
||||
body = line.lstrip()
|
||||
prefix = "#HttpOnly_" if body.startswith("#HttpOnly_") else ""
|
||||
@@ -5642,14 +5649,14 @@ class dl:
|
||||
if len(line_data) == 6:
|
||||
line_data.append("")
|
||||
if len(line_data) != 7:
|
||||
raise ValueError(f"{cookie_file} line {i + 1} is not a Netscape cookie row: {line!r}")
|
||||
raise ValueError(f"{source} line {i + 1} is not a Netscape cookie row: {line!r}")
|
||||
line_data[4] = ""
|
||||
line_data[1] = str(line_data[0].startswith(".")).upper()
|
||||
cookie_data[i] = prefix + "\t".join(line_data)
|
||||
cookie_data.insert(0, "# Netscape HTTP Cookie File")
|
||||
cookie_jar._really_load(StringIO("\n".join(cookie_data)), str(cookie_file), True, True) # type: ignore[attr-defined]
|
||||
cookie_jar._really_load(StringIO("\n".join(cookie_data)), source, True, True) # type: ignore[attr-defined]
|
||||
if not len(cookie_jar):
|
||||
raise ValueError(f"{cookie_file} holds no cookies; export it again in Netscape format")
|
||||
raise ValueError(f"{source} holds no cookies; export it again in Netscape format")
|
||||
return cookie_jar
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -3,9 +3,7 @@ import base64
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
@@ -15,7 +13,7 @@ from contextlib import suppress
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date as date_
|
||||
from functools import partial
|
||||
from http.cookiejar import CookieJar, MozillaCookieJar
|
||||
from http.cookiejar import CookieJar
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, cast
|
||||
|
||||
@@ -1146,24 +1144,18 @@ def server_account_cookies(service: str, profile: Optional[str]) -> Any:
|
||||
def load_client_cookies(cookie_text: Any) -> Optional[CookieJar]:
|
||||
"""Read the cookie jar a client sent with its request, or None when it sent none.
|
||||
|
||||
The client sends a Netscape cookie file compressed and base64 encoded, and
|
||||
``MozillaCookieJar`` reads from a path only, so the text goes through a temp file.
|
||||
The client sends a Netscape cookie file compressed and base64 encoded. It is parsed
|
||||
the way local ``dl`` parses a cookie file, so a file that works locally also works
|
||||
over ``--remote``.
|
||||
"""
|
||||
from unshackle.commands.dl import dl
|
||||
|
||||
if not cookie_text or not isinstance(cookie_text, str):
|
||||
return None
|
||||
|
||||
try:
|
||||
cookie_str = safe_inflate(base64.b64decode(cookie_text)).decode("utf-8")
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
|
||||
f.write(cookie_str)
|
||||
tmp_path = f.name
|
||||
try:
|
||||
jar = MozillaCookieJar(tmp_path)
|
||||
jar.load(ignore_discard=True, ignore_expires=True)
|
||||
return jar
|
||||
finally:
|
||||
with suppress(OSError):
|
||||
os.unlink(tmp_path)
|
||||
cookie_str = safe_inflate(base64.b64decode(cookie_text)).decode("utf-8-sig")
|
||||
return dl.parse_cookie_text(cookie_str, "client cookies")
|
||||
except (ValueError, zlib.error, OSError) as e:
|
||||
raise APIError(APIErrorCode.INVALID_INPUT, f"cookies is not a compressed Netscape cookie file: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user