viewer.html, viewer-url.js, and footer.tmpl are intended for Forgejo
contribution and carry MIT headers matching Forgejo's per-file convention.
ThatOpen @thatopen/components (loaded from CDN) is also MIT-licensed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The preview endpoint is co-hosted with a specific Forgejo instance, so it
should only be allowed to fetch from that host.
- Local file transport (ifc:///path) is always rejected with 403 — the service
must not read from the server's own filesystem
- New --allowed-hosts CLI option for `ifcurl serve` takes a comma-separated
list of permitted git hostnames (e.g. git.example.com or localhost:3000);
any unlisted host is rejected with 403
- Defense-in-depth: literal private/loopback/link-local IP addresses are
blocked even without an allowlist (169.254.x.x, 127.x, RFC1918, etc.)
- README and forgejo/README.md updated to include --allowed-hosts in the
standard deployment invocation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /proxy endpoint was removed in a previous session (ifcurl-cuh).
Remove it from the endpoints table in README.md.
New beads issues:
- ifcurl-2v9 P2: SSRF audit for /preview endpoint
- ifcurl-69l P3: licence headers for viewer.html, viewer-url.js, footer.tmpl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrite to lead with the vision and architecture, then cover each of the
four components (Python library+CLI, preview service, Forgejo integration,
browser viewer) with enough detail to understand what each does and how
to use it. Add a roadmap table showing phases 1-6. Keep the ifc:// URL
reference but trim it to the essentials — SPECIFICATION.md has the full
detail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the missing Go source files and a README covering the full apply
procedure. Previously only custom assets and the test file were tracked
here; ifc_url.go and the two-file diff were only in the Forgejo tree.
- forgejo/modules/markup/markdown/ifc_url.go — goldmark extension source
- forgejo/go.patch — diff for markdown.go + markup.go (7 lines total)
- forgejo/README.md — step-by-step: apply patch, run tests, build,
deploy assets, configure app.ini, upgrade procedure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7 tests in forgejo/modules/markup/markdown/ifc_url_test.go covering:
link form, bare URL, bare URL with underscores (sibling-node reassembly),
disabled when no service URL configured, HTML escaping, non-ifc links
unchanged, and inline-among-text placement.
Tests use goldmark.New() directly — no Forgejo DB/git test infrastructure
needed. Only dependencies are setting.IfcURL (ours) and goldmark (stable).
File lives in the same package as ifc_url.go and is part of the patch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parseIfcUrl, buildIfcUrl, toRawUrl extracted from viewer.html into
viewer-url.js so they can be imported and tested independently.
viewer.html imports from the module. 32 Node test-runner tests in
tests/test_viewer_url.mjs cover all three functions across GitHub,
GitLab, Forgejo/Gitea, SSH user@ prefix, and round-trip cases.
Caught a bug: + (selector union operator) was being URL-encoded as
%2B in buildIfcUrl; fixed by adding .replace(/%2B/g, "+") alongside
the existing comma fix, in both viewer-url.js and syncCameraUrl.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the raw renderer.clippingPlanes approach with OBC.Clipper:
URL-specified clip= planes are seeded as draggable SimplePlanes.
Toolbar '✂ clip' toggle + double-click on model surface to add planes;
'⊗ clips' button (shown when planes exist) to clear all. syncCameraUrl
now serialises current plane state back to the ifc:// URL on every
camera move or plane drag/create/delete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applyClipPlanes() parses all repeatable clip= params (6 floats: point +
normal in IFC world coords), transforms each via toThree() into Three.js
space, and sets world.renderer.three.clippingPlanes. syncCameraUrl already
preserves clip= params when rebuilding the URL on camera movement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applySelector() classifies elements via OBC.Classifier.byCategory(), then
maps the selector type names to matching categories using prefix matching
(so IfcWall also covers IfcWallStandardCase), and calls hider.isolate() to
show only matching elements. Supports simple type names and + union syntax.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a second toolbar row with labelled fields for repo, ref, path,
and selector. Editing any field rebuilds and reloads the ifc:// URL.
The raw URL in row 1 remains editable and copyable. Closes ifcurl-yju.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- toRawUrl() now handles SSH user@ prefix, GitHub (raw.githubusercontent.com),
GitLab (/-/raw/), and Forgejo/Gitea; strips heads/tags/ prefix for hosts
that don't use gitnamespace form
- FOV input in toolbar: shows current FOV, editing it updates the camera
and syncs the fov= param in the ifc:// URL
- applyCameraParam now restores fov= from URL when placing camera
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Shrink viewer div to start below toolbar (CSS var --toolbar-h via
ResizeObserver) so the canvas never overlaps the input area
- Disable camera controls on input focus, re-enable on blur
- Stop pointer event propagation from input to prevent camera-controls
capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace url-display span with editable input; Enter or drag-and-drop loads
a new ifc:// URL by navigating to the same page with updated ?url= param
- Fix syncCameraUrl: convert Three.js → IFC world coords (x,-z,y) before
serialising camera= param so URLs are in Z-up IFC space
- Apply IFC → Three.js transform (x,z,-y) when placing camera from URL param
- Drop viewer.py: viewer is now a Forgejo static file; service.py has no
/viewer or /proxy endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Viewer is now a Forgejo static file; IFC bytes are fetched directly from
Forgejo by the browser. The proxy is no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viewer.html is now served directly from Forgejo at /assets/viewer.html,
fetching IFC bytes via Forgejo's raw URL (same-origin, auth inherited).
footer.tmpl updated to point to /assets/viewer.html instead of the
ifcurl service. No proxy needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /viewer?url=ifc://... serves a self-contained HTML page that loads
@thatopen/components v3 from CDN and renders the IFC model in WebGL
- GET /proxy?url=<raw> proxies IFC bytes from Forgejo to the browser,
avoiding CORS restrictions
- forgejo/templates/custom/footer.tmpl injects a "View in 3D" button on
.ifc file view pages in Forgejo; deploy to
/var/lib/forgejo/custom/templates/custom/footer.tmpl
CDN loading required several non-obvious fixes documented in viewer.py:
web-ifc loaded from unpkg (not esm.sh) to avoid the Node.js process polyfill
that breaks the browser ST build's environment check; fragments worker wrapped
in a blob URL for Firefox cross-origin worker compatibility; model.box used for
camera fit before tiles exist; fragments.core.update(true) to flush tile queue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /preview requires a JSON body so cannot be used in <img src>.
The new GET /preview?url=... delegates to the same handler and shares
all caching and authentication behaviour.
- url.py: preserve non-standard ports in host field; ifc://host:3000/...
now correctly generates https://host:3000/... rather than dropping :3000
- git.py: fall back from https to http on clone/fetch failure, enabling
local Gitea instances running on plain HTTP; inject tokens into http://
URLs as well as https://; extract _clone_bare() helper
- tests/test_url.py: three new tests for port preservation in host,
git_remote_url with non-standard HTTPS port, and SSH with non-standard port
- dev/compose.yml: Gitea via podman-compose, SQLite, ports 3000/2222
- INSTALL_LOCK skips the web installer; all config via env vars
- Named volume ifcurl-gitea-data persists across restarts
- .gitignore: exclude dev/gitea-data/ if it appears
Start: podman-compose -f dev/compose.yml up -d
Stop: podman-compose -f dev/compose.yml down
Reset: podman-compose -f dev/compose.yml down -v
Generated with the assistance of an AI coding tool.
- ifcurl/auth.py: token config (~/.config/ifcurl/tokens.json),
get_token_for_host(), inject_token() for HTTPS URL credential injection
- ifcurl/git.py: thread token= through fetch_ifc/fetch_ifc_bytes/_open_remote;
authenticated fetch uses 'git fetch <auth_url> +refs/*:refs/*' so the
token is never written to on-disk git config
- ifcurl/service.py: optional token field in PreviewRequest; request token
takes precedence over config-file token; get_token_for_host imported at
module level for clean monkeypatching in tests
- tests
Generated with the assistance of an AI coding tool.
- ifcurl/service.py: FastAPI POST /preview → image/png with three
caching tiers (bytes LRU, GUID-set LRU, PNG filesystem)
- ifcurl/git.py: add fetch_ifc() returning (hexsha, bytes); refactor
around _get_repo() and _read_commit_blob() helpers
- ifcurl/__main__.py: add 'ifcurl serve' subcommand; no-args prints
full help; all subcommands have descriptions and examples; URL format
and parameter reference in every help page
Generated with the assistance of an AI coding tool.