freenet 0.2.120

Freenet core software
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Freenet shell smoke-test fixture</title>
</head>
<body>
<!--
  This page is published as a Freenet website contract and loaded inside the
  shell's sandboxed iframe by the Playwright smoke tests
  (crates/core/tests/playwright/tests/shell.spec.ts).

  It deliberately does NOT include the shell bridge, the WebSocket shim, or the
  navigation interceptor: the freenet node injects those into the served HTML
  (see crates/core/src/server/path_handlers.rs::sandbox_content_body). The
  point of the test is to exercise that injected code against a real browser,
  so the fixture only provides the DOM the injected code acts on.

  Stable element ids are the test contract — keep them in sync with
  shell.spec.ts.
-->
<h1 id="title">Freenet shell fixture</h1>

<!--
  Cross-origin link WITH an explicit new-window target. The interceptor must
  leave this to the browser: the shell iframe carries
  `allow-popups-to-escape-sandbox`, so the native popup is a real top-level
  document with a proper Origin (freenet/river#208's null-origin CORS breakage
  came from the popup INHERITING the sandbox, which no longer happens). Routing
  it through the shell instead is what broke Firefox, whose popup blocker
  refuses `window.open` from a `message` handler.

  example.com is a reserved documentation domain (RFC 2606); the tests stub the
  route so no real network request is made.
-->
<a id="cross-origin-link" href="https://example.com/external" target="_blank" rel="noopener">
  External cross-origin link
</a>

<!--
  Cross-origin link with NO target. Left native it would navigate the app frame
  itself to a foreign origin, which the shell's `frame-src 'self'` refuses — the
  click would silently do nothing. The interceptor must open a tab for it, from
  inside the click handler where the user gesture is live.
-->
<a id="cross-origin-untargeted-link" href="https://example.com/plain">
  External cross-origin link, no target
</a>

<!--
  Cross-origin link with `target="_top"`. `_top` names an ANCESTOR context, not
  a new one, and the sandbox forbids navigating it — so "any non-`_self` target
  is a new-window activation, hand it to the browser" makes this a silently dead
  click. It must be classified like an untargeted cross-origin link and opened
  in a tab.
-->
<a id="cross-origin-top-link" href="https://example.com/top" target="_top">
  External cross-origin link, target=_top
</a>

<!--
  Same-origin link with an explicit new-window target. This is the #5087 case:
  every cross-CONTRACT link is same-ORIGIN, so this is what a thumbnail or
  cross-app link looks like. It must open natively as a real top-level tab that
  loads the shell (previously it either produced a blank sandbox-inheriting tab,
  or — once routed through `open_url` — nothing at all in Firefox).
-->
<a id="same-origin-blank-link" href="page2.html" target="_blank">
  In-contract page 2, new tab
</a>

<!--
  Same-origin, in-contract link. The interceptor must turn this into a
  `navigate` postMessage so the shell performs an in-place iframe hop instead
  of a full reload. The href is relative so it resolves under this contract's
  web prefix regardless of the contract key.
-->
<a id="same-origin-link" href="page2.html">In-contract page 2</a>

<!--
  Download link. The interceptor must NOT intercept links carrying a
  `download` attribute; `handleAnchorClick` early-returns on `download`
  (path_handlers.rs:2013), so no open_url / navigate postMessage is sent and
  the link keeps its native (download) behaviour.

  The href is SAME-ORIGIN (a data URL would be skipped earlier by the
  javascript:/data: protocol check at path_handlers.rs:2011, which would NOT
  isolate the `download` guard). Without the `download` attribute this exact
  same-origin link would be intercepted as a `navigate` (see #same-origin-link);
  the `download` attribute is what makes the early-return fire instead, so the
  absence of a `navigate` postMessage on click is attributable to the
  `download` guard specifically. The test reads `#download-link` directly and
  does not rely on what the browser does natively after the early-return.
-->
<a id="download-link" href="page2.html" download="hello.txt">Download</a>

<!--
  Programmatic open. A contract calling window.open() from its own JS never
  touches the anchor interceptor, so it is the path that relies purely on
  `allow-popups-to-escape-sandbox` to produce a real top-level tab. Before
  #5100 an override forwarded it to the shell (#4645); now it is native.
-->
<button id="programmatic-open">window.open()</button>

<!--
  The #3818 escape, played out by a hostile contract. `allow-popups-to-escape-sandbox`
  means a popup this frame opens is top-level with NO sandboxing flags, and an
  `about:blank` popup inherits this frame's origin — so the contract can script
  it and, from there, re-embed its own bytes in a nested frame that inherits no
  sandboxing either. If the server does not sandbox contract content itself,
  that frame runs at the node's real origin.
-->
<button id="escape-sandbox">escape</button>

<!-- Result sink the Playwright tests read via page.evaluate. -->
<pre id="poll-result">pending</pre>

<script>
  // Exercise the CSP `connect-src 'self'` directive that #3842 fixed: a
  // same-origin fetch to the permission poller endpoint. If the shell CSP
  // regresses to `ws: wss:` only, the browser blocks this fetch and emits a
  // "Content-Security-Policy: blocked ... (connect-src)" console error, which
  // the Playwright test asserts is absent.
  //
  // We surface the outcome in #poll-result so the test can also confirm the
  // fetch actually ran (a CSP block rejects the promise rather than returning
  // a response).

  // Programmatic new-window open, invoked from a real click so the gesture is
  // live (the same condition a contract app's own button would have).
  document.getElementById('programmatic-open').addEventListener('click', function () {
    window.open('https://example.com/programmatic', '_blank');
  });

  // Attempt the escape described above. Each step records what it managed to
  // do, so the test can tell "blocked" apart from "never ran".
  document.getElementById('escape-sandbox').addEventListener('click', function () {
    var w = window.open('about:blank', 'escape-probe');
    window.__escapeOpened = !!w;
    if (!w) return;
    try {
      // Probe BOTH routes that serve contract bytes, because they are guarded
      // in different places:
      //   - `page2.html` resolved against the base drops `?__sandbox=1`, so it
      //     lands on the plain asset route (`variable_content`) — as
      //     `Sec-Fetch-Dest: iframe`, the destination a `document`-only guard
      //     does not see.
      //   - `index.html?__sandbox=1` is the sandbox-content route
      //     (`serve_sandbox_response`), which blocks TOP-LEVEL loads but not
      //     nested ones, and which relied entirely on the iframe attribute for
      //     its opaque origin. This is the sharper attack: no scriptable SVG or
      //     other exotic type needed, just the contract's own page.
      var asset = new URL('page2.html', document.baseURI).href;
      var app = new URL('index.html?__sandbox=1', document.baseURI).href;
      w.document.write(
        '<!doctype html><title>escape</title>' +
        '<iframe id="probe-asset" src="' + asset + '"></iframe>' +
        '<iframe id="probe-app" src="' + app + '"></iframe>'
      );
      w.document.close();
      window.__escapeWrote = true;
    } catch (err) {
      window.__escapeWriteError = String((err && err.name) || err);
    }
  });

  (function () {
    var sink = document.getElementById('poll-result');
    fetch('/permission/pending', { headers: { 'accept': 'application/json' } })
      .then(function (resp) { sink.textContent = 'fetched:' + resp.status; })
      .catch(function (err) { sink.textContent = 'error:' + (err && err.message); });
  })();
</script>
</body>
</html>