* fix(links): use resolve_absolute for remote document paths
* fix(ui): refuse output file names that end in a dot or a space
* ci: check that git dependencies come from storytold
* ci: release only with git dependencies from storytold
* ci: check git dependency sources on a locked, parsed Cargo.lock
The git-sources step in ci.yml and release.yml fails when Cargo.lock is
missing, when .cargo/config.toml replaces or patches a source, when
Cargo.lock is out of date with the manifests (cargo metadata --locked),
and when any package source parsed from Cargo.lock as TOML is a git
source outside github.com/storytold/. A grep on Cargo's usual spelling
missed single-quoted strings and stale lockfiles.
* fix(links): a Windows path on Unix is refused, not reported as needing a source
resolve_remote_document_path reported MissingSourcePath for any path that
resolved but not to an absolute one. With an absolute source, such a path
is one this platform cannot open (C:\x.pdf on Unix): it is now
NotLocal(Invalid). A relative target from a missing or relative source
still gives MissingSourcePath.
* ci: check the legacy .cargo/config for source replacement too
Cargo reads .cargo/config as well as .cargo/config.toml, so the
git-sources step greps both; grep -s keeps a missing file from
failing the step.
* ci: parse cargo config for source replacement, patches and path overrides
GoToR file specifications go through craft-docpath: network, device and
object-manager paths (\??\, /??/) are refused without touching the
filesystem, relative targets resolve against the source document's
folder, and a result that is not absolute is refused rather than read
against the working directory. Files named on the command line open
under their absolute path, so their relative links keep resolving.
* M7: open local GoToR PDF links natively
* GoToR links: read only regular .pdf files under a size cap; add missing translations
A GoToR link in an untrusted PDF could name /dev/zero (unbounded read), a FIFO
(UI thread hangs) or a directory. Both the UI path and the `link_open` tool now
go through `pdfcraft_engine::read_remote_document`, which requires a `.pdf`
extension and a regular file (metadata checked before opening) of at most
1 GiB, and stops reading if the file grows past the cap. Regression tests cover
directories, a /dev/zero symlink and a FIFO (unix) in the engine, the tool and
the UI.
Adds the four link notifications to all 20 catalogs and "The existing action
will be preserved." to el, es, pl, te and zh-hant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Security: refuse email links that attach or insert a local file
Follow-up to #104. A document's mailto: link could ask the email app to
attach a file from the user's computer (Evolution, KMail and Claws Mail
honour attach=, Evolution and KMail also attachment=) or read one into
the body (Claws Mail's insert=).
document_url now refuses a mailto: address in which any word starting
with attach or insert (any case) is followed by =, wherever it stands
and after each layer of percent-decoding. It also refuses text that
some email tools decode before reading the address: RFC 2047 encoded
words, which KMail decodes across the whole address, and backslash
escapes, which xdg-email's echo decodes. The link is refused rather
than rewritten: PdfCraft doesn't change what a document asks to open,
and the email apps' parsers disagree with one another.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Security: show a link's host as the browser receives it, in punycode
Follow-up to #104. The link prompt's host now comes from
links::display_host, which reads it with url::Url: the webbrowser crate
hands the OS that crate's serialisation, and the web build's browser
parses by the same WHATWG standard. International hosts are shown in
punycode with a note, a label mixing writing systems (UTS #39 resolved
script sets) gets a red warning, numeric hosts such as 3232235777 show
as 192.168.1.1, and a host the browser would reject is refused as
malformed. The Unicode form isn't repeated in the prompt, so a
whole-script lookalike can't read as the real site.
url, idna and icu_properties become direct dependencies of
pdfcraft-engine; all three were already in the graph.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Security: refuse document addresses the URL parser rejects
Follow-up to #104. egui opens links through the webbrowser crate, which
parses the address with url::Url and, when that fails, treats it as a
local file path and opens file:///<cwd>/<address> in the browser.
document_url accepted addresses that don't parse, such as
mailto://a^b/x or https://a^b/ (a forbidden host code point), so a
document could still get the browser to open a local path.
document_url now also requires url::Url to accept the address with the
same scheme, which is exactly the parse webbrowser does first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A document could open any address with no confirmation: a link click, a
push button's URI action, a one-line button script mapped to a URI action,
or app.launchURL from JavaScript (on by default). That allowed sending
form data to an outside site, or handing file:, smb: and app-handler
addresses to the operating system.
All four now go through PdfCraftApp::request_document_url:
- engine::links::document_url accepts only http, https and mailto, up to
2048 characters, and refuses control, zero-width, bidi-override and
non-space whitespace characters, malformed schemes and web addresses
without a host.
- Accepted addresses wait in an "Open this web page?" dialog that shows
the real host (engine::links::host drops user:password@, and treats \
like / as browsers do, so https://a.example\@b.example shows a.example)
and the full address. Cancel is the highlighted button; Escape and
clicking outside cancel. While one request waits, further ones are
dropped, so a script can't stack dialogs.
- Refused addresses are reported in a notice and never opened.
PdfCraft's own links (Help, About, updates) still use open_url.
Preferences > JavaScript now says scripts need permission to open a web
page. Tests: engine unit tests for the policy and host parsing, and
crates/ui-egui/tests/document_links.rs driving the link, button, button
script, app.launchURL and Escape paths (all fail against the old code).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>