mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 21:15:06 +00:00
fix(dl): repair damaged cookie files without rewriting them
Space separators, a BOM, a missing header and #HttpOnly_ rows now load; repairs happen in memory and an unparseable file raises instead of loading as empty.
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Cookie files that use spaces instead of tabs must still load."""
|
||||
|
||||
from http.cookiejar import CookieJar
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from unshackle.commands.dl import dl
|
||||
|
||||
HEADER = "# Netscape HTTP Cookie File\n"
|
||||
|
||||
|
||||
def _names(jar) -> dict:
|
||||
return {c.name: c.value for c in jar}
|
||||
|
||||
|
||||
def test_space_separated_cookie_file(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + ".example.com TRUE / TRUE 1234567890 sid abc123\n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_tab_separated_value_keeps_spaces(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(
|
||||
HEADER + "\t".join([".example.com", "TRUE", "/", "TRUE", "1234567890", "sid", "a b c"]) + "\n", "utf8"
|
||||
)
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "a b c"}
|
||||
|
||||
|
||||
def test_space_separated_empty_value(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + ".example.com TRUE / TRUE 1234567890 sid \n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": ""}
|
||||
|
||||
|
||||
def test_space_separated_httponly(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + "#HttpOnly_.example.com TRUE / TRUE 1234567890 sid abc123\n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_mixed_separators_in_one_file(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(
|
||||
HEADER
|
||||
+ "\t".join([".a.com", "TRUE", "/", "TRUE", "1234567890", "tabbed", "1"])
|
||||
+ "\n.b.com TRUE / TRUE 1234567890 spaced 2\n",
|
||||
"utf8",
|
||||
)
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"tabbed": "1", "spaced": "2"}
|
||||
|
||||
|
||||
def test_malformed_row_is_reported(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + ".a.com\tTRUE\t/\tTRUE\t1\tsid\tv\nnot a cookie\n", "utf8")
|
||||
|
||||
with pytest.raises(ValueError, match="line 3"):
|
||||
dl.load_cookie_file(file)
|
||||
|
||||
|
||||
def test_missing_header_is_added(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(".example.com\tTRUE\t/\tTRUE\t1234567890\tsid\tabc123\n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_utf8_bom_is_ignored(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_bytes(b"\xef\xbb\xbf" + (HEADER + ".example.com\tTRUE\t/\tTRUE\t1234567890\tsid\tabc123\n").encode())
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_json_export_gives_clear_error(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text('[{"domain": ".example.com", "name": "sid", "value": "abc123"}]\n', "utf8")
|
||||
|
||||
with pytest.raises(ValueError, match="JSON export"):
|
||||
dl.load_cookie_file(file)
|
||||
|
||||
|
||||
def test_leading_blank_line_is_not_json(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text("\n" + HEADER + ".example.com TRUE / TRUE 1234567890 sid abc123\n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_dot_flag_mismatch_does_not_abort_file(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + "example.com\tTRUE\t/\tTRUE\t1\tsid\tv\n", "utf8")
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "v"}
|
||||
|
||||
|
||||
def test_the_users_file_is_never_rewritten(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
original = ".example.com TRUE / TRUE 1234567890 sid abc123\n"
|
||||
file.write_text(original, "utf8")
|
||||
|
||||
dl.load_cookie_file(file)
|
||||
|
||||
assert file.read_text("utf8") == original
|
||||
|
||||
|
||||
def test_save_cookies_onto_an_unrepaired_file(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(".example.com TRUE / TRUE 1234567890 sid abc123\n", "utf8")
|
||||
|
||||
dl.save_cookies(file, CookieJar())
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
|
||||
|
||||
def test_tabbed_row_is_never_resplit_on_spaces(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + ".a.com\tTRUE\t/\tFALSE\thello world\n", "utf8")
|
||||
|
||||
with pytest.raises(ValueError, match="not a Netscape cookie row"):
|
||||
dl.load_cookie_file(file)
|
||||
|
||||
|
||||
def test_file_without_cookies_is_reported(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + "# nothing else here\n", "utf8")
|
||||
|
||||
with pytest.raises(ValueError, match="holds no cookies"):
|
||||
dl.load_cookie_file(file)
|
||||
|
||||
|
||||
def test_round_trip_keeps_httponly_and_blank_expiry(tmp_path: Path) -> None:
|
||||
file = tmp_path / "cookies.txt"
|
||||
file.write_text(HEADER + "#HttpOnly_.example.com TRUE / TRUE 1234567890 sid abc123\n", "utf8")
|
||||
|
||||
dl.save_cookies(file, CookieJar())
|
||||
|
||||
assert _names(dl.load_cookie_file(file)) == {"sid": "abc123"}
|
||||
assert "#HttpOnly_" in file.read_text("utf8")
|
||||
Reference in New Issue
Block a user