webget-cli 0.16.0__tar.gz → 0.16.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webget_cli-0.16.0/webget_cli.egg-info → webget_cli-0.16.1}/PKG-INFO +1 -1
- {webget_cli-0.16.0 → webget_cli-0.16.1}/pyproject.toml +1 -1
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_auth.py +20 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_webget.py +23 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/cache.py +4 -1
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/http.py +2 -1
- {webget_cli-0.16.0 → webget_cli-0.16.1/webget_cli.egg-info}/PKG-INFO +1 -1
- {webget_cli-0.16.0 → webget_cli-0.16.1}/LICENSE +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/README.md +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/setup.cfg +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_cache.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_concurrency.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_http.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_mcp.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_ssrf.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_auth_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_auth_state.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_base64_strip.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_browser_discovery.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_browser_ssrf.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cache_cap.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cache_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_crawl.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_engine.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_parse_opts.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_provenance.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_concurrency_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_crawler.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_map.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_sitemap_regex.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_ssrf.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_doctor.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_extraction.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_extraction_markdown.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_feed_markdown.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_firecrawl_policy.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_firecrawl_status.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_frontier.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_health.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_integration_ladder.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ladder_honesty.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ladder_retry.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_login_flow.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_crawl.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_engine.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_leak_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_map.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_metadata.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_profile.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_provenance.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_server.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_smoke.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_metadata.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_nonhtml.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_profile_logout_storage.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_engine.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_failover.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_normalize.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_provenance.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_searxng.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_security_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_size_review.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ssrf_dual_dns.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_truncate.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/__init__.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/browser.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/cli.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/crawler.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/discovery.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/extraction.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/firecrawl.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/frontier.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/health.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/ladder.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/profile.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/search.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/ssrf.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/truncate.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/SOURCES.txt +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/dependency_links.txt +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/entry_points.txt +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/requires.txt +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/top_level.txt +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.py +0 -0
- {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_mcp.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webget-cli
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: Local search + scrape CLI with an HTTP fast path, optional browser fallback, and authenticated session profiles. Zero API keys.
|
|
5
5
|
Author-email: David Tarigan <tarigansdavid@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "webget-cli"
|
|
7
|
-
version = "0.16.
|
|
7
|
+
version = "0.16.1"
|
|
8
8
|
description = "Local search + scrape CLI with an HTTP fast path, optional browser fallback, and authenticated session profiles. Zero API keys."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -128,6 +128,26 @@ class TestSessionReuse:
|
|
|
128
128
|
res = asyncio.run(_fetch(server.url("/cookie-gated"), cookies=ck))
|
|
129
129
|
assert _one(res)["status"] == "success"
|
|
130
130
|
|
|
131
|
+
def test_epoch_zero_cookie_is_expired_not_session(self, fresh_cache, isolated_env):
|
|
132
|
+
"""expires == 0 is an already-expired cookie (epoch 1970), not a session.
|
|
133
|
+
|
|
134
|
+
The old jar build used ``expires or -1``, which turned an explicit 0
|
|
135
|
+
into -1 and made the cookie look like a non-expiring session cookie, so
|
|
136
|
+
an expired cookie was still sent. Session cookies are represented by a
|
|
137
|
+
missing expires or -1 (see profile.py), never by 0.
|
|
138
|
+
"""
|
|
139
|
+
server = fresh_cache
|
|
140
|
+
ck = [_cookie("session", server.host, expires=0)]
|
|
141
|
+
res = asyncio.run(_fetch(server.url("/cookie-gated"), cookies=ck))
|
|
142
|
+
assert _one(res)["status"] == "blocked"
|
|
143
|
+
|
|
144
|
+
def test_absent_expiry_is_a_session_cookie(self, fresh_cache, isolated_env):
|
|
145
|
+
"""A cookie with no `expires` key is a session cookie and IS sent."""
|
|
146
|
+
server = fresh_cache
|
|
147
|
+
ck = [{"name": "session", "value": "v", "domain": server.host, "path": "/"}]
|
|
148
|
+
res = asyncio.run(_fetch(server.url("/cookie-gated"), cookies=ck))
|
|
149
|
+
assert _one(res)["status"] == "success"
|
|
150
|
+
|
|
131
151
|
|
|
132
152
|
class TestProfileMeta:
|
|
133
153
|
def test_corrupt_state_reports_corrupt(self, isolated_env):
|
|
@@ -49,6 +49,29 @@ class TestParseOpts:
|
|
|
49
49
|
assert r2 == ["u", "https://x.com"]
|
|
50
50
|
assert c2 is not None
|
|
51
51
|
|
|
52
|
+
def test_cookies_httponly_prefix_is_parsed(self, tmp_path):
|
|
53
|
+
"""Netscape HttpOnly lines start with '#HttpOnly_' and must be parsed.
|
|
54
|
+
|
|
55
|
+
The parser skips any line starting with '#', so without stripping the
|
|
56
|
+
prefix those cookies would be silently dropped.
|
|
57
|
+
"""
|
|
58
|
+
ck = tmp_path / "ck.txt"
|
|
59
|
+
ck.write_text(
|
|
60
|
+
"# Netscape HTTP Cookie File\n"
|
|
61
|
+
"#HttpOnly_.x.com\tTRUE\t/\tFALSE\t0\ts\tv\n"
|
|
62
|
+
)
|
|
63
|
+
_, c1, *_ = opts("-c", str(ck), "u", "https://x.com")
|
|
64
|
+
assert c1 is not None and len(c1) == 1
|
|
65
|
+
assert c1[0]["name"] == "s" and c1[0]["httpOnly"] is True
|
|
66
|
+
# a real comment line is still ignored
|
|
67
|
+
ck.write_text(
|
|
68
|
+
"# Netscape HTTP Cookie File\n"
|
|
69
|
+
"# a comment\n"
|
|
70
|
+
"#HttpOnly_.x.com\tTRUE\t/\tFALSE\t0\ts\tv\n"
|
|
71
|
+
)
|
|
72
|
+
_, c2, *_ = opts("-c", str(ck), "u", "https://x.com")
|
|
73
|
+
assert c2 is not None and len(c2) == 1 and c2[0]["httpOnly"] is True
|
|
74
|
+
|
|
52
75
|
def test_max_chars_aliases(self):
|
|
53
76
|
_, _, _, mc1, *_ = opts("-n", "500", "u", "https://x.com")
|
|
54
77
|
_, _, _, mc2, *_ = opts("--max-chars", "500", "u", "https://x.com")
|
|
@@ -58,6 +58,9 @@ def parse_cookie_file(path):
|
|
|
58
58
|
with open(os.path.expanduser(path)) as f:
|
|
59
59
|
for line in f:
|
|
60
60
|
line = line.strip()
|
|
61
|
+
http_only = line.startswith("#HttpOnly_")
|
|
62
|
+
if http_only:
|
|
63
|
+
line = line[len("#HttpOnly_") :]
|
|
61
64
|
if not line or line.startswith("#"):
|
|
62
65
|
continue
|
|
63
66
|
parts = line.split("\t")
|
|
@@ -70,7 +73,7 @@ def parse_cookie_file(path):
|
|
|
70
73
|
"domain": domain,
|
|
71
74
|
"path": path_,
|
|
72
75
|
"secure": secure == "TRUE",
|
|
73
|
-
"httpOnly":
|
|
76
|
+
"httpOnly": http_only,
|
|
74
77
|
}
|
|
75
78
|
if expires not in ("0", "Session", ""):
|
|
76
79
|
try:
|
|
@@ -336,7 +336,8 @@ async def fetch_http(url, max_chars, cookies=None, headers=None, timeout=15):
|
|
|
336
336
|
for c in cookies:
|
|
337
337
|
d = (c.get("domain") or "").lstrip(".").lower()
|
|
338
338
|
if d and (host == d or host.endswith("." + d)):
|
|
339
|
-
exp = c.get("expires")
|
|
339
|
+
exp = c.get("expires")
|
|
340
|
+
exp = -1 if exp is None else exp
|
|
340
341
|
# Skip expired cookies: session cookies (expires<0) and
|
|
341
342
|
# future-expiry cookies are sent; past-expiry are not.
|
|
342
343
|
if 0 <= exp < now:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webget-cli
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: Local search + scrape CLI with an HTTP fast path, optional browser fallback, and authenticated session profiles. Zero API keys.
|
|
5
5
|
Author-email: David Tarigan <tarigansdavid@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|