rewrite data-*url attributes, where Gitea hands URLs to its own JavaScript

The dashboard heatmap reads its endpoint from data-heatmap-url:
document.querySelector('#user-heatmap').getAttribute('data-heatmap-url').
Unprefixed it fetched from the PLATFORM's origin, got the SPA's index.html and
reported "Unexpected token '<'" while parsing JSON — the last error left after
everything visible was rendering correctly.

data-url carries the language switcher's links and had the same problem.

Matched on the `url` suffix rather than by listing attribute names, because the
list is Gitea's to change.

Found by reading Gitea's own bundle rather than by reasoning about what it
probably does — which is what the three previous attempts at this error were.
This commit is contained in:
officer
2026-08-19 09:38:08 +00:00
parent 413b7ac0e9
commit fe67de0281
2 changed files with 28 additions and 2 deletions
+18
View File
@@ -24,6 +24,24 @@ describe('attributes', () => {
expect(out).toContain(`action="${BASE}/repo/new"`);
});
// Gitea hands URLs to its own JavaScript through data attributes, which is where the dashboard heatmap
// gets its endpoint — `document.querySelector('#user-heatmap').getAttribute('data-heatmap-url')`.
// Unprefixed it fetched from the PLATFORM's origin, got the SPA's index.html, and reported
// "Unexpected token '<'" while parsing JSON. Found by reading Gitea's bundle, not by guessing.
test('data-*url attributes are rewritten — the heatmap reads its endpoint from one', () => {
const html = '<div id="user-heatmap" data-heatmap-url="/api/v1/users/x/heatmap">';
expect(rewriteAttributes(html, BASE)).toContain(`data-heatmap-url="${BASE}/api/v1/users/x/heatmap"`);
});
test('and plain data-url, which the language switcher uses', () => {
expect(rewriteAttributes('<a data-url="/?lang=en-US">', BASE)).toContain(`data-url="${BASE}/?lang=en-US"`);
});
test('a data attribute that is not a url is left alone', () => {
const html = '<div data-tooltip-content="/not a url">';
expect(rewriteAttributes(html, BASE)).toBe(html);
});
test('a relative href is already correct and must not be touched', () => {
expect(rewriteAttributes('<a href="settings">', BASE)).toBe('<a href="settings">');
});
+10 -2
View File
@@ -58,8 +58,16 @@ const isForeign = (path: string): boolean => path.startsWith('//') || /^[a-z][a-
export function rewriteAttributes(html: string, base: string): string {
const skip = (path: string) => isForeign(path) || path === base || path.startsWith(`${base}/`);
return html
.replace(/(href|src|action)=("|')(\/[^"']*)\2/gi, (match, attr: string, quote: string, path: string) =>
skip(path) ? match : `${attr}=${quote}${prefixed(base, path)}${quote}`,
// `data-*url` alongside the obvious three. Gitea hands URLs to its own JavaScript through data
// attributes — `data-heatmap-url` is where the dashboard heatmap gets its endpoint, and `data-url`
// carries the language switcher's links. Missing them meant the heatmap fetched from the PLATFORM's
// origin, got the SPA's index.html, and reported `Unexpected token '<'` while parsing JSON.
//
// Matched on the `url` suffix rather than by listing names, because the list is Gitea's to change.
.replace(
/(href|src|action|data-[a-z-]*url)=("|')(\/[^"']*)\2/gi,
(match, attr: string, quote: string, path: string) =>
skip(path) ? match : `${attr}=${quote}${prefixed(base, path)}${quote}`,
)
.replace(/url\((\/[^)"']*)\)/g, (match, path: string) => (skip(path) ? match : `url(${prefixed(base, path)})`));
}