fix(liveuamap): preserve safe marker and wrapper semantics

This commit is contained in:
Shadowbroker
2026-08-18 16:08:32 -06:00
parent d3a2b55fed
commit 6990fc1961
4 changed files with 57 additions and 14 deletions
+9 -8
View File
@@ -113,14 +113,9 @@ def _collect(
out.append(marker)
return
# Common wrapper shapes returned by APIs or page-side serialization.
for key in _WRAPPER_KEYS:
if key in value and isinstance(value[key], (dict, list, str)):
_collect(value[key], out, depth=depth + 1, inherited_id=None)
return
# A direct marker is accepted even if coordinates are absent here; the
# provider formatter performs the final coordinate/range validation.
# Coordinate-bearing objects are markers even if they also contain a field
# named `data`/`events`. Check them before generic wrapper traversal so a
# legitimate marker cannot be swallowed by wrapper heuristics.
if _looks_like_marker(value):
marker = dict(value)
if inherited_id and not marker.get("id"):
@@ -128,6 +123,12 @@ def _collect(
out.append(marker)
return
# Common wrapper shapes returned by APIs or page-side serialization.
for key in _WRAPPER_KEYS:
if key in value and isinstance(value[key], (dict, list, str)):
_collect(value[key], out, depth=depth + 1, inherited_id=None)
return
# Some versions expose a dictionary keyed by marker ID. Traverse mapping
# values while preserving the key as a fallback identifier.
traversable = [
+24 -6
View File
@@ -274,9 +274,9 @@ def _fetch_liveuamap_browser() -> list[dict[str, Any]]:
page.wait_for_timeout(5_000)
html = page.content()
# Try the useful payload before classifying the page as
# a challenge. Normal pages may legitimately load a
# Turnstile asset; valid marker data should win.
# Try useful marker state before classifying the page as
# a challenge. Normal pages may load Turnstile assets;
# valid marker data should win over that heuristic.
payload = _read_page_payload(page, html)
if payload is None:
if _looks_like_challenge(html):
@@ -341,6 +341,26 @@ def _fetch_liveuamap_browser() -> list[dict[str, Any]]:
return []
def _normalize_marker_link(raw: Any, base_url: str) -> str:
"""Resolve only HTTP(S) marker links; reject active/non-web URL schemes."""
text = _as_text(raw).strip()
if not text:
return ""
try:
parsed = urlparse(text)
except ValueError:
return ""
if parsed.scheme:
return text if parsed.scheme.lower() in {"http", "https"} else ""
try:
resolved = urljoin(base_url.rstrip("/") + "/", text)
resolved_scheme = urlparse(resolved).scheme.lower()
except ValueError:
return ""
return resolved if resolved_scheme in {"http", "https"} else ""
def _format_markers(
candidates: list[dict[str, Any]],
*,
@@ -374,9 +394,7 @@ def _format_markers(
image = _as_text(marker.get("img") or marker.get("image") or marker.get("photo") or "").strip()
source = _as_text(marker.get("source") or marker.get("src") or "").strip()
event_time = marker.get("time", marker.get("t", marker.get("timestamp", "")))
link = _as_text(marker.get("link") or marker.get("url") or "").strip()
if link and not urlparse(link).scheme:
link = urljoin(base_url.rstrip("/") + "/", link.lstrip("/"))
link = _normalize_marker_link(marker.get("link") or marker.get("url") or "", base_url)
raw_id = marker.get("id", marker.get("event_id"))
marker_id = _as_text(raw_id).strip() if raw_id is not None else ""
+13
View File
@@ -44,6 +44,19 @@ def test_common_wrapper_shape():
assert _ids(payload) == ["wrapped"]
def test_coordinate_marker_wins_over_wrapper_named_field():
payload = {
"id": "direct",
"lat": 10,
"lng": 20,
"data": {"diagnostic": "metadata, not a marker wrapper"},
}
markers = normalize_liveuamap_payload(payload)
assert len(markers) == 1
assert markers[0]["id"] == "direct"
assert markers[0]["lat"] == 10
def test_legacy_urlencoded_base64_json():
raw = json.dumps([{"id": "legacy", "lat": 1, "lng": 2}]).encode()
payload = quote(base64.b64encode(raw).decode())
+11
View File
@@ -73,6 +73,17 @@ def test_api_query_token_is_not_exposed_as_marker_fallback(monkeypatch):
assert "super-secret" not in repr(markers)
def test_non_http_marker_link_is_rejected():
markers = scraper._format_markers(
[{"id": "evt", "lat": 10, "lng": 20, "link": "javascript:alert(1)"}],
region="Ukraine",
base_url="https://liveuamap.com",
provider="browser",
)
assert markers[0]["link"] == "https://liveuamap.com"
assert "javascript:" not in repr(markers)
def test_api_redirect_is_refused_before_following_credentials(monkeypatch):
monkeypatch.setenv("LIVEUAMAP_API_URL", "https://api.example.test/events")
monkeypatch.setattr(