mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-21 01:47:37 +02:00
fix(liveuamap): preserve safe marker and wrapper semantics
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user