Skip to content

Commit 7b0e5e2

Browse files
authored
fix(svg): escape tower tooltip titles (JhaSourav07#1129)
2 parents 8a50ac5 + 61ef744 commit 7b0e5e2

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

lib/svg/generator.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,25 @@ describe('generateSVG', () => {
226226
expect(svg).toMatch(/style="animation-delay: \d+\.\d+s;"/);
227227
});
228228

229+
it('escapes XML-reserved characters in tower tooltip titles', () => {
230+
const calendarWithUnsafeDate = {
231+
weeks: [
232+
{
233+
contributionDays: [{ contributionCount: 3, date: '2024-06-12 & <bad>' }],
234+
},
235+
],
236+
} as ContributionCalendar;
237+
238+
const svg = generateSVG(
239+
mockStats,
240+
{ user: 'avi' } as unknown as BadgeParams,
241+
calendarWithUnsafeDate
242+
);
243+
244+
expect(svg).toContain('<title>TODAY: 2024-06-12 &amp; &lt;bad&gt;: 3 contributions</title>');
245+
expect(svg).not.toContain('<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>');
246+
});
247+
229248
it('includes reduced-motion CSS for the scan line in the main SVG output', () => {
230249
const svg = generateSVG(mockStats, { user: 'avi' } as unknown as BadgeParams, mockCalendar);
231250

@@ -374,6 +393,21 @@ describe('generateSVG', () => {
374393
expect(svg).toContain('@keyframes grow-up');
375394
expect(svg).toMatch(/style="animation-delay: \d+\.\d+s;"/);
376395
});
396+
397+
it('escapes XML-reserved characters in auto-theme tower tooltip titles', () => {
398+
const calendarWithUnsafeDate = {
399+
weeks: [
400+
{
401+
contributionDays: [{ contributionCount: 3, date: '2024-06-12 & <bad>' }],
402+
},
403+
],
404+
} as ContributionCalendar;
405+
406+
const svg = generateSVG(mockStats, autoParams, calendarWithUnsafeDate);
407+
408+
expect(svg).toContain('<title>TODAY: 2024-06-12 &amp; &lt;bad&gt;: 3 contributions</title>');
409+
expect(svg).not.toContain('<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>');
410+
});
377411
});
378412

379413
// Ghost City Placeholder Mode tests

lib/svg/generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ function renderTowers(towerData: TowerData[], accent: string, text: string, sf:
190190
<g transform="translate(${t.x}, ${t.y})">
191191
<g class="cp-tower" style="animation-delay: ${delay}s;">
192192
${t.isTodayWithCommits ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
193-
<title>${t.tooltip}</title>
193+
<title>${escapeXML(t.tooltip)}</title>
194194
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" fill="${color}" fill-opacity="${t.faceOpacity.left}" stroke="${color}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
195195
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" fill="${color}" fill-opacity="${t.faceOpacity.right}" stroke="${color}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
196196
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" fill="${color}" fill-opacity="${t.faceOpacity.top}" stroke="${color}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
@@ -398,7 +398,7 @@ function generateAutoThemeSVG(
398398
<g transform="translate(${t.x}, ${t.y})">
399399
<g class="cp-tower" style="animation-delay: ${delay}s;">
400400
${t.isTodayWithCommits ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
401-
<title>${t.tooltip}</title>
401+
<title>${escapeXML(t.tooltip)}</title>
402402
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.left}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
403403
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.right}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
404404
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.top}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />

0 commit comments

Comments
 (0)