Skip to content

Implements Global URL Scheme Overrides - #13544

Open
Shkarlatov wants to merge 12 commits into
keepassxreboot:developfrom
Shkarlatov:feature/url_overrides
Open

Implements Global URL Scheme Overrides#13544
Shkarlatov wants to merge 12 commits into
keepassxreboot:developfrom
Shkarlatov:feature/url_overrides

Conversation

@Shkarlatov

Copy link
Copy Markdown

Implements Global URL Scheme Overrides, similar to KeePass 2.x's "URL Overrides" feature (see #10887). Adds a new Settings page where users can map a URL scheme (e.g. ssh, kdbx, ftp) to an external cmd:// command template. When an entry's URL matches an enabled rule's scheme, KeePassXC runs that command (with the existing confirmation dialog) instead of opening the URL in the browser.

Implementation notes

  • The feature lives in a new optional subproject, src/urloverride/ (static library urloverride), gated by a new KPXC_FEATURE_URLOVERRIDE CMake option (default ON, disabled under KPXC_MINIMAL) — same pattern as KPXC_FEATURE_BROWSER/SSHAGENT/FDOSECRETS.
  • DatabaseWidget::openUrlForEntry() checks the configured rules for the entry's URL scheme before falling back to the existing cmd:///kdbx:///default-browser handling. A new "URL Overrides" page is registered in the Settings dialog.
  • Scheme matching is a literal, case-insensitive comparison against the URL's own scheme (QUrl::scheme()) — not a regular expression — so ssh never accidentally matches sshfs. The first enabled rule with a non-empty command wins; a rule with an empty command is skipped rather than blocking a lower-priority rule for the same scheme.
  • A disabled example rule (sshcmd://ssh {USERNAME}@{URL:HOST}) is seeded once on first use, so the feature and its placeholder syntax are discoverable in the settings page without doing anything until a user explicitly enables it.
  • Saving changes to the rule table shows a confirmation dialog first, since these rules can cause arbitrary commands to run when a URL is opened.
  • On Windows, launching a cmd:// command now detects console-subsystem targets (e.g. ssh.exe) via their PE header and forces a new console for them specifically, so they get a visible window and can prompt for passwords/host-key confirmations interactively — while GUI targets (e.g. a browser) are left unaffected.

Screenshots

Not included — built and tested in a headless CI-style environment, no screen capture available for this session. UI surface added is a single new "URL Overrides" page under Settings: a 3-column table (Enabled / URL Scheme / Command) with Add / Remove / Move Up / Move Down buttons and an explanatory label.

Testing strategy

  • New testurloverride unit test target (guiless, built only when KPXC_FEATURE_URLOVERRIDE is on, same as testbrowser/testsshagent), covering:
    • rule list persistence (order, disabled entries, overwriting with a shorter/empty list, distinguishing "never configured" from "explicitly saved as empty")
    • the one-time seeded example rule, and that it never matches on its own (disabled)
    • scheme normalization (ff:// / ff: / ff all normalize to ff — confirming it's not treated as a regex)
    • matching logic: literal case-insensitive comparison, first-match-wins ordering, empty scheme/command being skipped (not blocking lower-priority rules), URLs with no scheme never matching
    • special characters (&, <, >, ", ') in a command surviving a save/load round-trip
  • Manually tested end-to-end on Windows: adding a rule for ssh pointing at C:\Windows\System32\OpenSSH\ssh.exe, confirming the execute-command dialog appears, and confirming a real visible console window opens (verified the Windows console-allocation behavior specifically, since that requires real process/console state and isn't meaningfully unit-testable).
  • The settings page's widget logic (table CRUD, save-confirmation gating) is not covered by an automated GUI test — consistent with the rest of the settings pages in this codebase (ShortcutSettingsPage, BrowserSettingsPage), none of which have dedicated GUI tests either.

Introduces the CMake option for an optional global URL scheme override
feature, following the existing KPXC_FEATURE_BROWSER/SSHAGENT/FDOSECRETS
pattern: option declaration, KPXC_MINIMAL opt-out, feature summary, and
the config-keepassx.h define. No consumer yet.
New optional static library (src/urloverride, built only when
KPXC_FEATURE_URLOVERRIDE is on) that maps a URL scheme (e.g. "ssh",
"kdbx") to an external "cmd://" command template, plus a settings page
to manage the rule table (enable, scheme, command, reordering, with a
confirmation prompt before saving changes).

- UrlOverride::getRules/setRules persist the rule list as an XML string
  under a single new Config key (UrlOverride_Rules), the same way
  KeeShare stores its structured settings - no other change to
  core/Config, and no second QSettings instance on the config file.
- UrlOverride::findCommand does a literal, case-insensitive match of a
  rule's scheme against the URL's own scheme (not a regex), first
  enabled match with a non-empty command wins.
- A disabled example rule ("ssh" -> "cmd://ssh {USERNAME}@{URL:HOST}")
  is seeded once, on first-ever use, purely for discoverability.
- UrlOverride::executeCommand launches the resolved command; on
  Windows it detects console-subsystem targets (e.g. ssh.exe) via the
  PE header and forces a new console so they get a visible window,
  without doing so for GUI targets like a browser.
DatabaseWidget::openUrlForEntry() now checks UrlOverride::findCommand()
for the entry's URL scheme before falling back to the existing
cmd://, kdbx:// and default-browser handling, and the existing cmd://
launch path now goes through UrlOverride::executeCommand() to pick up
the Windows console-visibility fix. Both are guarded by
KPXC_FEATURE_URLOVERRIDE with an unchanged fallback when the feature
is built out.

MainWindow registers the new "URL Overrides" settings page alongside
the existing Shortcuts page.
New guiless testurloverride target (built only when
KPXC_FEATURE_URLOVERRIDE is on, like testbrowser/testsshagent), covering:
- rule list round-trip through Config, including order, disabled
  entries, and overwriting with a shorter/empty list
- the one-time disabled example rule seeded on first use
- scheme normalization (not a regex: "ff://" stores as "ff")
- literal case-insensitive scheme matching, first-match-wins ordering,
  empty scheme/command being skipped rather than blocking lower rules,
  and URLs with no scheme never matching
- XML special characters in a command surviving the save/load
  round-trip
@droidmonkey

droidmonkey commented Jul 24, 2026

Copy link
Copy Markdown
Member

This should not be gated behind a build flag. It should be directly incorporated into the application settings.

Was this generated by AI (I assume so given the writeup and headless development).

Did you test this yourself (as a human)? Why no screenshots?

@Shkarlatov

Copy link
Copy Markdown
Author

Yes, I mostly used AI, but I was in complete control of it.

That feature was the only thing that kept me using Keepass2.

изображение изображение

@Shkarlatov

Copy link
Copy Markdown
Author
clideo_editor_18f556db45e34a5a97276f6a5fc98462

@droidmonkey

Copy link
Copy Markdown
Member

Thank you

@varjolintu varjolintu added the pr: ai-assisted Pull request contains significant contributions by generative AI label Jul 25, 2026
Comment thread src/urloverride/UrlOverride.cpp Outdated
…-form input

Extracts the URI scheme grammar (ALPHA *(ALPHA / DIGIT / "+" / "-" / ".")
per RFC 3986) from a string instead of guessing which surrounding
characters to strip. Handles messy input (stray leading characters,
doubled-up separators, trailing garbage that isn't ":"/"/") that a
naive trim-based approach would get wrong.
UrlOverride::normalizeScheme() duplicated logic that belongs in
UrlTools, a general-purpose URL utility namespace used elsewhere in
the app. Drop the plugin's own copy and call UrlTools::normalizeScheme()
instead; the plugin's own tests now only cover that setRules() applies
it before persisting, the extraction grammar itself is covered by
TestUrlTools.
@Shkarlatov

Copy link
Copy Markdown
Author

Linux: no automatic terminal handling

Unlike Windows, we intentionally do not attempt to detect or wrap console-subsystem targets on Linux. ELF binaries have no PE-style subsystem flag, and there's no OS-level "hidden console" to reveal — QProcess::startDetached() simply inherits the parent's stdin/stdout/stderr, which for a normally-launched desktop app are typically not a real TTY. As a result, interactive commands (e.g. ssh prompting for a password or host-key confirmation) will generally fail fast rather than work, the same underlying issue as on Windows, just surfacing differently (no window vs. no TTY).

If a rule needs an interactive terminal on Linux, the recommended workaround is to specify a terminal emulator explicitly in the command, e.g.:

cmd://xterm -e ssh {USERNAME}@{URL:HOST}

We're deliberately not auto-detecting a terminal emulator for now (no single portable way to do this across distros/DEs). Once xdg-terminal-exec (part of the XDG spec) is more broadly available, that would be the natural, portal-friendly way to implement this properly instead of guessing which terminal emulator is installed.

isConsoleSubsystemExecutable() was checked against the PATH-resolved
program, but QProcess was then started with the original, unresolved
name, so the console-subsystem check and the launched binary could
diverge.
Add bounds checks that reject malformed or truncated executables
before trusting offsets/sizes taken from the file itself: e_lfanew
is validated against the file size before seeking to it, and
SizeOfOptionalHeader is checked against both the declared and the
actual IMAGE_OPTIONAL_HEADER32/64 size before reading it. Prevents
misreading Subsystem out of a corrupt or truncated PE.
Add a link to the Entry Placeholders documentation section instead of
listing placeholder examples inline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: ai-assisted Pull request contains significant contributions by generative AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants