From f90dc3a3ff6542de8621974617c4b97c807c9777 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Sun, 21 Jun 2026 05:46:35 +0300 Subject: [PATCH] test(share): extract + cover injectTrackerHead (#100, #98) Extract the admin trackerHead injection into a pure injectTrackerHead() and test it: a snippet containing $&/$$/backtick-dollar survives BYTE-FOR-BYTE (pins the function-replacer fix), empty/whitespace/undefined and a missing leave the html unchanged. Co-Authored-By: Claude Opus 4.8 --- .../share/inject-tracker-head.util.spec.ts | 60 +++++++++++++++++++ .../core/share/inject-tracker-head.util.ts | 30 ++++++++++ .../src/core/share/share-seo.controller.ts | 29 +++++---- 3 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 apps/server/src/core/share/inject-tracker-head.util.spec.ts create mode 100644 apps/server/src/core/share/inject-tracker-head.util.ts diff --git a/apps/server/src/core/share/inject-tracker-head.util.spec.ts b/apps/server/src/core/share/inject-tracker-head.util.spec.ts new file mode 100644 index 00000000..8bf32eb9 --- /dev/null +++ b/apps/server/src/core/share/inject-tracker-head.util.spec.ts @@ -0,0 +1,60 @@ +import { injectTrackerHead } from './inject-tracker-head.util'; + +// Pins the public-share trackerHead injection invariant (ShareSeoController). +// The admin snippet is trusted content and MUST land byte-for-byte before the +// first . The critical regression these tests guard is the function vs +// string replacer: a string replacement interprets `$&`/`$$`/`` $` ``/`$'` +// inside the snippet as substitution patterns and mangles the tracker. The +// byte-for-byte test below FAILS on the old string-replacer implementation and +// passes only with the function replacer. + +const HTML = 'tb'; + +describe('injectTrackerHead', () => { + it('inserts the snippet immediately before the first ', () => { + const out = injectTrackerHead(HTML, ''); + expect(out).toBe( + 't\nb', + ); + }); + + it('inserts a snippet containing $& byte-for-byte (function replacer)', () => { + const snippet = ''; + const out = injectTrackerHead(HTML, snippet); + expect(out).toContain(`${snippet}\n`); + // The literal "$&" survives; a string replacer would have spliced in the + // matched "" here. + expect(out).toContain('$&'); + expect(out).not.toContain('"'); + }); + + it('inserts a snippet containing $$, $` and $\' byte-for-byte', () => { + // All four special replacement patterns in one snippet. + const snippet = ""; + const out = injectTrackerHead(HTML, snippet); + expect(out).toContain(`${snippet}\n`); + }); + + it('returns html unchanged for an empty trackerHead', () => { + expect(injectTrackerHead(HTML, '')).toBe(HTML); + }); + + it('returns html unchanged for a whitespace-only trackerHead', () => { + expect(injectTrackerHead(HTML, ' \n\t ')).toBe(HTML); + }); + + it('returns html unchanged for an undefined trackerHead', () => { + expect(injectTrackerHead(HTML, undefined)).toBe(HTML); + }); + + it('returns html unchanged when there is no marker', () => { + const noHead = 'no head here'; + expect(injectTrackerHead(noHead, '')).toBe(noHead); + }); + + it('injects before only the FIRST when several exist', () => { + const twoHeads = ''; + const out = injectTrackerHead(twoHeads, 'X'); + expect(out).toBe('X\n'); + }); +}); diff --git a/apps/server/src/core/share/inject-tracker-head.util.ts b/apps/server/src/core/share/inject-tracker-head.util.ts new file mode 100644 index 00000000..58828ef8 --- /dev/null +++ b/apps/server/src/core/share/inject-tracker-head.util.ts @@ -0,0 +1,30 @@ +/** + * Injects an admin-authored analytics/tracker snippet verbatim into the + * of a public-share page. + * + * `trackerHead` is admin-only trusted content (writable only via the + * admin-gated workspace settings) and must be inserted BYTE-FOR-BYTE before the + * first `` marker. A plain string replacement would interpret `$&`, + * `$$`, `` $` `` and `$'` inside the snippet as substitution patterns and mangle + * the tracker, so a FUNCTION replacer is used: its return value is inserted + * literally with no special-pattern interpretation. + * + * The snippet is deliberately NOT escaped (it is trusted HTML/JS). Returns the + * html unchanged when: + * - trackerHead is undefined / empty / whitespace-only, or + * - there is no `` marker to anchor the injection. + */ +export function injectTrackerHead( + html: string, + trackerHead: string | undefined, +): string { + if (typeof trackerHead !== 'string' || trackerHead.trim().length === 0) { + return html; + } + if (!html.includes('')) { + return html; + } + // Function replacer: the return value is inserted literally, so `$&`/`$$`/ + // `` $` ``/`$'` in the admin snippet are NOT treated as substitution patterns. + return html.replace('', () => `${trackerHead}\n`); +} diff --git a/apps/server/src/core/share/share-seo.controller.ts b/apps/server/src/core/share/share-seo.controller.ts index 1c443dcc..db20ef0a 100644 --- a/apps/server/src/core/share/share-seo.controller.ts +++ b/apps/server/src/core/share/share-seo.controller.ts @@ -8,6 +8,7 @@ import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo'; import { EnvironmentService } from '../../integrations/environment/environment.service'; import { Workspace } from '@docmost/db/types/entity.types'; import { htmlEscape } from '../../common/helpers/html-escaper'; +import { injectTrackerHead } from './inject-tracker-head.util'; @Controller('share') export class ShareSeoController { @@ -97,21 +98,19 @@ export class ShareSeoController { // pages only. It is trusted content, so it is NOT escaped. The htmlEmbed // block itself is sandboxed and is the safe surface for everyone else. const trackerHead = (workspace?.settings as any)?.trackerHead; - if (typeof trackerHead === 'string' && trackerHead.trim().length > 0) { - if (transformedHtml.includes('')) { - // Function replacer: the snippet is admin-authored trusted content and - // must be injected verbatim. A string replacement would interpret `$&`, - // `$'`, `` $` `` and `$$` inside it as substitution patterns and mangle - // the tracker; a function return value is inserted literally. - transformedHtml = transformedHtml.replace( - '', - () => `${trackerHead}\n`, - ); - } else { - this.logger.warn( - 'trackerHead is configured but no marker was found in the share index HTML; tracker snippet was not injected.', - ); - } + const beforeInjection = transformedHtml; + transformedHtml = injectTrackerHead(transformedHtml, trackerHead); + if ( + beforeInjection === transformedHtml && + typeof trackerHead === 'string' && + trackerHead.trim().length > 0 + ) { + // A non-empty snippet was configured but nothing was injected: the only + // reason injectTrackerHead leaves the html unchanged for a non-empty + // snippet is a missing marker. + this.logger.warn( + 'trackerHead is configured but no marker was found in the share index HTML; tracker snippet was not injected.', + ); } res.type('text/html').send(transformedHtml);