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.
Files changed (85) hide show
  1. {webget_cli-0.16.0/webget_cli.egg-info → webget_cli-0.16.1}/PKG-INFO +1 -1
  2. {webget_cli-0.16.0 → webget_cli-0.16.1}/pyproject.toml +1 -1
  3. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_auth.py +20 -0
  4. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_webget.py +23 -0
  5. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/cache.py +4 -1
  6. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/http.py +2 -1
  7. {webget_cli-0.16.0 → webget_cli-0.16.1/webget_cli.egg-info}/PKG-INFO +1 -1
  8. {webget_cli-0.16.0 → webget_cli-0.16.1}/LICENSE +0 -0
  9. {webget_cli-0.16.0 → webget_cli-0.16.1}/README.md +0 -0
  10. {webget_cli-0.16.0 → webget_cli-0.16.1}/setup.cfg +0 -0
  11. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_cache.py +0 -0
  12. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_concurrency.py +0 -0
  13. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_http.py +0 -0
  14. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_mcp.py +0 -0
  15. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_adversarial_ssrf.py +0 -0
  16. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_auth_review.py +0 -0
  17. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_auth_state.py +0 -0
  18. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_base64_strip.py +0 -0
  19. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_browser_discovery.py +0 -0
  20. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_browser_ssrf.py +0 -0
  21. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cache_cap.py +0 -0
  22. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cache_review.py +0 -0
  23. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_crawl.py +0 -0
  24. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_engine.py +0 -0
  25. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_parse_opts.py +0 -0
  26. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_cli_provenance.py +0 -0
  27. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_concurrency_review.py +0 -0
  28. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_crawler.py +0 -0
  29. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_map.py +0 -0
  30. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_sitemap_regex.py +0 -0
  31. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_discovery_ssrf.py +0 -0
  32. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_doctor.py +0 -0
  33. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_extraction.py +0 -0
  34. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_extraction_markdown.py +0 -0
  35. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_feed_markdown.py +0 -0
  36. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_firecrawl_policy.py +0 -0
  37. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_firecrawl_status.py +0 -0
  38. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_frontier.py +0 -0
  39. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_health.py +0 -0
  40. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_integration_ladder.py +0 -0
  41. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ladder_honesty.py +0 -0
  42. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ladder_retry.py +0 -0
  43. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_login_flow.py +0 -0
  44. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_crawl.py +0 -0
  45. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_engine.py +0 -0
  46. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_leak_review.py +0 -0
  47. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_map.py +0 -0
  48. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_metadata.py +0 -0
  49. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_profile.py +0 -0
  50. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_provenance.py +0 -0
  51. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_server.py +0 -0
  52. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_mcp_smoke.py +0 -0
  53. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_metadata.py +0 -0
  54. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_nonhtml.py +0 -0
  55. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_profile_logout_storage.py +0 -0
  56. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_engine.py +0 -0
  57. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_failover.py +0 -0
  58. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_normalize.py +0 -0
  59. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_search_provenance.py +0 -0
  60. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_searxng.py +0 -0
  61. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_security_review.py +0 -0
  62. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_size_review.py +0 -0
  63. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_ssrf_dual_dns.py +0 -0
  64. {webget_cli-0.16.0 → webget_cli-0.16.1}/tests/test_truncate.py +0 -0
  65. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/__init__.py +0 -0
  66. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/browser.py +0 -0
  67. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/cli.py +0 -0
  68. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/crawler.py +0 -0
  69. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/discovery.py +0 -0
  70. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/extraction.py +0 -0
  71. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/firecrawl.py +0 -0
  72. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/frontier.py +0 -0
  73. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/health.py +0 -0
  74. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/ladder.py +0 -0
  75. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/profile.py +0 -0
  76. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/search.py +0 -0
  77. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/ssrf.py +0 -0
  78. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget/truncate.py +0 -0
  79. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/SOURCES.txt +0 -0
  80. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/dependency_links.txt +0 -0
  81. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/entry_points.txt +0 -0
  82. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/requires.txt +0 -0
  83. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.egg-info/top_level.txt +0 -0
  84. {webget_cli-0.16.0 → webget_cli-0.16.1}/webget_cli.py +0 -0
  85. {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.0
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.0"
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": False,
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") or -1
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.0
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