Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ uvx xbrlkit view NVDA
A browser cannot be handed a local path — `file://` is unreachable from an
https page, and a file input cannot be pre-populated — so this serves the
document instead, on an ephemeral loopback port with an unguessable path, and
opens `xbrlkit.com/?url=…` pointing at it. `http://127.0.0.1` is a
opens `xbrlkit.com/view?url=…` pointing at it (earlier releases open
`xbrlkit.com/?url=…`, which keeps working). `http://127.0.0.1` is a
potentially trustworthy origin, so the https page may read it; the CORS header
names the viewer's origin and no other. The document is readable there, by that
origin, until you press Ctrl-C. `--viewer` points at a different build.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ def test_view_filing_serves_it_for_the_viewer(loaded: LoadedFiling) -> None:
try:
out = tools.view_filing(loaded, "holon", viewers)
assert out["filing"] == loaded.id and out["bytes"] > 0
assert out["viewer_url"].startswith(f"{DEFAULT_VIEWER}/?url=")
assert out["viewer_url"].startswith(f"{DEFAULT_VIEWER}/view?url=")
assert out["document_url"] in out["viewer_url"]
assert out["document_url"].startswith("http://127.0.0.1:")
with pytest.raises(tools.ToolError):
Expand Down
14 changes: 12 additions & 2 deletions tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from xbrlkit.view import (
DEFAULT_VIEWER,
VIEWER_PAGE,
ViewerHost,
origin_of,
serve_report,
Expand Down Expand Up @@ -71,13 +72,22 @@ def test_the_path_carries_an_unguessable_token(served) -> None:


def test_the_viewer_url_names_the_document(served) -> None:
assert served.viewer_url.startswith(f"{DEFAULT_VIEWER}/?url=")
assert served.viewer_url.startswith(f"{DEFAULT_VIEWER}/view?url=")
assert served.file_url in served.viewer_url


def test_the_link_opens_the_viewers_view_page() -> None:
# `/view`, not `/`: the viewer's analytics see the path and drop the query,
# so the path is what tells an open from xbrlkit apart from a web link.
assert VIEWER_PAGE == "view"
assert viewer_url(DEFAULT_VIEWER, "http://127.0.0.1:1/t/a.json") == (
"https://xbrlkit.com/view?url=http://127.0.0.1:1/t/a.json"
)


def test_viewer_url_and_origin_helpers() -> None:
assert viewer_url("https://example.test/", "http://127.0.0.1:1/a.json") == (
"https://example.test/?url=http://127.0.0.1:1/a.json"
"https://example.test/view?url=http://127.0.0.1:1/a.json"
)
assert origin_of("https://example.test/path?q=1") == "https://example.test"
with pytest.raises(ValueError):
Expand Down
14 changes: 12 additions & 2 deletions xbrlkit/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
The only machinery this needs, then, is a CORS header, because the viewer's
origin is not this one.

**The link.** It opens the viewer's ``/view`` page, ``https://xbrlkit.com/view?url=…``.
The viewer reads ``/view`` and ``/`` the same way: a ``?url=`` on either opens the
document. The path is what separates an open from here from a report link on the
web, because the viewer's page-view analytics record the path and drop the query
string. Earlier releases open ``/?url=…``, which the viewer keeps serving for good.

**Posture.** Loopback only, one document per path, and each path carries an
unguessable token — so the document is readable by the viewer origin, for as
long as the process runs, and not by every other page the browser has open.
Expand All @@ -40,6 +46,10 @@
# origin, so the old name must keep working for those installs.
DEFAULT_VIEWER = "https://xbrlkit.com"

# The viewer page a link from xbrlkit opens: `/view` rather than `/`, so the
# viewer can count these opens apart from report links on the web.
VIEWER_PAGE = "view"

_CONTENT_TYPES = {
".jsonld": "application/ld+json",
".json": "application/json",
Expand All @@ -55,8 +65,8 @@ def origin_of(url: str) -> str:


def viewer_url(viewer: str, file_url: str) -> str:
"""The viewer page that opens ``file_url`` — the ``?url=`` link."""
return f"{viewer.rstrip('/')}/?url={quote(file_url, safe=':/')}"
"""The viewer's ``/view`` page, opening ``file_url`` — the ``?url=`` link."""
return f"{viewer.rstrip('/')}/{VIEWER_PAGE}?url={quote(file_url, safe=':/')}"


class _Handler(BaseHTTPRequestHandler):
Expand Down