Keep a share link's own item however long its name runs

`shareDeepLink` now builds through `sharedViewLink`, whose length budget
applied to the first item too: a parameter that alone overran it was
dropped, and the link came out as a bare `?shared=`. A name of a few
hundred characters — the GUI allows five hundred, and encoding triples
every non-ASCII one — was enough, so that item's own link in the mail
landed on Shared with nothing picked out, where it used to open the item.

The first item now always goes in; the budget only decides how many more
join it. One long link is still the item the mail is about, and it is no
worse than a bare origin for the clients that truncate it.
This commit is contained in:
Nariman Jelveh
2026-08-24 16:18:28 -07:00
parent 3f9dc7c030
commit 081cb888ec
2 changed files with 28 additions and 4 deletions
@@ -121,6 +121,28 @@ describe('sharedViewLink', () => {
);
});
// A name can run to hundreds of characters, and encoding multiplies
// non-ASCII ones; the link exists to name the item, so it always does,
// however long — the length cap only limits how many more join it.
it('keeps the first item even when it alone outgrows the length cap', () => {
const long = `/alice/${UID}/${'\u5831\u544a'.repeat(120)}.pdf`;
const short = `/alice/${UID}/a.txt`;
expect(shareDeepLink('https://puter.com', long).length).toBeGreaterThan(
SHARE_DEEP_LINK_MAX_LENGTH,
);
expect(
new URL(
shareDeepLink('https://puter.com', long),
).searchParams.getAll('shared'),
).toEqual([long]);
// Nothing fits after it, and nothing later is taken instead.
expect(
new URL(
sharedViewLink('https://puter.com', [long, short]),
).searchParams.getAll('shared'),
).toEqual([long]);
});
it('names an item once however often it was queued', () => {
const path = `/alice/${UID}/a.txt`;
expect(sharedViewLink('https://puter.com', [path, path])).toBe(
+6 -4
View File
@@ -62,9 +62,10 @@ export const SHARE_DEEP_LINK_ITEMS_LIMIT = 20;
/**
* How long a link may run, in characters. Somewhere past two thousand, older
* mail clients cut a URL off or stop making it clickable and this is the
* button so items are added only while the whole link stays within this. A
* single item always fits: a username, a uuid and a filename come to a few
* hundred, and it takes long names for the count above to matter first.
* button so items are added only while the whole link stays within this. The
* first item goes in regardless: a link that names nothing is no better than
* the origin, and one long name (hundreds of characters, tripled by encoding
* when non-ASCII) is still the item the mail is about.
*/
export const SHARE_DEEP_LINK_MAX_LENGTH = 2000;
@@ -84,7 +85,8 @@ export const sharedViewLink = (origin: string, paths: string[]): string => {
if (params.length === SHARE_DEEP_LINK_ITEMS_LIMIT) break;
const param = `${SHARE_DEEP_LINK_PARAM}=${encodeURIComponent(path)}`;
const added = param.length + (params.length === 0 ? 0 : '&'.length);
if (length + added > SHARE_DEEP_LINK_MAX_LENGTH) break;
const overLength = length + added > SHARE_DEEP_LINK_MAX_LENGTH;
if (params.length > 0 && overLength) break;
params.push(param);
length += added;
}