From e8920b2cf37ace0c0f3fb347d266c1308944074d Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 21 Aug 2026 15:22:55 -0400 Subject: [PATCH] Do not add the mount to a site URL that already has it a381174 fixed a link missing its subpath and produced one carrying it twice: https:///shopdb/shopdb/s/. site_base_url is DOCUMENTED as scheme + host, and sites really do set the full base including the subpath. Trusting the description rather than tolerating what is configured is the same mistake as the last one, pointed the other way. The mount is now appended only when the configured URL does not already end with it, which covers every combination a site can have: base with the subpath, base without it, a trailing slash on either, and a site mounted at the root where there is no subpath at all. Checked all five by evaluating the expression rather than reasoning about it. --- plugins/tools/frontend/views/SecureSendCreate.vue | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/tools/frontend/views/SecureSendCreate.vue b/plugins/tools/frontend/views/SecureSendCreate.vue index 5c2621a8..591acd99 100644 --- a/plugins/tools/frontend/views/SecureSendCreate.vue +++ b/plugins/tools/frontend/views/SecureSendCreate.vue @@ -151,12 +151,19 @@ async function createLink() { // withBase for the path: a site served under /shopdb or /ops carries that // in front of every route, and leaving it off produced a 404 from IIS for a // secret that existed - with the sender shown a link that could never work. - const origin = (await getSiteBaseUrl()).replace(/\/+$/, '') - const path = withBase(`/s/${token}`) + // site_base_url is DOCUMENTED as scheme + host, but sites really do set the + // full base including the subpath - and appending the mount to one that + // already carries it produced /shopdb/shopdb/s/. Tolerate both + // rather than trusting the description: add the mount only when it is not + // there already. + const configured = (await getSiteBaseUrl()).replace(/\/+$/, '') + const mount = withBase('/').replace(/\/+$/, '') + const needsMount = mount && !configured.endsWith(mount) // The key rides in the FRAGMENT. Browsers never send it, so it reaches the // recipient and nowhere else. - link.value = `${origin}${path}#${sealed.fragmentKey}` + link.value = + `${configured}${needsMount ? mount : ''}/s/${token}#${sealed.fragmentKey}` // Drop the plaintext as soon as it is sealed: no reason to keep it in memory // behind the link that replaced it. plaintext.value = ''