mirror of
https://github.com/ThePhaseless/Byparr.git
synced 2026-09-24 14:20:08 +01:00
fix(v1): source User-Agent from request headers; evaluate only as fallback
page.evaluate runs eval() in the page's main world, which fails with 'call to eval() blocked by CSP' under any CSP that disallows unsafe-eval - HTTP headers (already stripped), meta tags (not strippable), or internal viewer documents (#394). The navigation request already carries the UA the site actually saw, so take user_agent from page_request.request.headers and keep evaluate only as a best-effort fallback whose failure can no longer 500 the request.
This commit is contained in:
+16
-1
@@ -153,10 +153,25 @@ async def read_item(request: LinkRequest, dep: BrowserDep) -> LinkResponse:
|
|||||||
else await dep.page.content()
|
else await dep.page.content()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The User-Agent the target site actually saw, taken from the navigation
|
||||||
|
# request headers. page.evaluate falls through to eval() in the page's
|
||||||
|
# main world and fails whenever the page CSP disallows eval (e.g. the
|
||||||
|
# Firefox JSON viewer in #394, or uncatchable meta-tag CSP), so evaluate
|
||||||
|
# is only a fallback and its failure must not turn the request into a 500.
|
||||||
|
user_agent = (
|
||||||
|
page_request.request.headers.get("user-agent") if page_request else None
|
||||||
|
)
|
||||||
|
if user_agent is None:
|
||||||
|
try:
|
||||||
|
user_agent = await dep.page.evaluate("navigator.userAgent")
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Could not determine User-Agent via page.evaluate")
|
||||||
|
user_agent = ""
|
||||||
|
|
||||||
return LinkResponse(
|
return LinkResponse(
|
||||||
message="Success",
|
message="Success",
|
||||||
solution=Solution(
|
solution=Solution(
|
||||||
user_agent=await dep.page.evaluate("navigator.userAgent"),
|
user_agent=user_agent,
|
||||||
url=final_url if final_url is not None else dep.page.url,
|
url=final_url if final_url is not None else dep.page.url,
|
||||||
status=status,
|
status=status,
|
||||||
cookies=cookies,
|
cookies=cookies,
|
||||||
|
|||||||
+22
-1
@@ -139,7 +139,9 @@ def fake_dep(*, fail_states: set[str] | None = None) -> BrowserDepClass:
|
|||||||
page = AsyncMock()
|
page = AsyncMock()
|
||||||
page.url = "https://example.test/login"
|
page.url = "https://example.test/login"
|
||||||
page.goto.return_value = MagicMock(
|
page.goto.return_value = MagicMock(
|
||||||
status=HTTPStatus.OK, headers={"content-type": "text/html"}
|
status=HTTPStatus.OK,
|
||||||
|
headers={"content-type": "text/html"},
|
||||||
|
request=MagicMock(headers={"user-agent": "UnitTestBrowser/1.0"}),
|
||||||
)
|
)
|
||||||
page.title.return_value = "Login"
|
page.title.return_value = "Login"
|
||||||
page.evaluate.return_value = "UnitTestBrowser/1.0"
|
page.evaluate.return_value = "UnitTestBrowser/1.0"
|
||||||
@@ -181,3 +183,22 @@ async def test_domcontentloaded_timeout_returns_408():
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert exc.value.status_code == HTTPStatus.REQUEST_TIMEOUT
|
assert exc.value.status_code == HTTPStatus.REQUEST_TIMEOUT
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_user_agent_survives_csp_blocked_evaluate():
|
||||||
|
"""UA comes from request headers when page CSP blocks evaluate (#394).
|
||||||
|
|
||||||
|
No CSP configuration (header, meta tag, or internal viewer document) may
|
||||||
|
turn /v1 into a 500.
|
||||||
|
"""
|
||||||
|
dep = fake_dep()
|
||||||
|
dep.page.evaluate.side_effect = Exception("call to eval() blocked by CSP")
|
||||||
|
|
||||||
|
response = await read_item(
|
||||||
|
LinkRequest(url="https://example.test/login"),
|
||||||
|
dep,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status == "ok"
|
||||||
|
assert response.solution.user_agent == "UnitTestBrowser/1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user