@@ -3,6 +3,7 @@ import { I18n } from "../common/I18n.js";
33import { getCardColors } from "../common/color.js" ;
44import { CustomError } from "../common/error.js" ;
55import { kFormatter } from "../common/fmt.js" ;
6+ import { encodeHTML } from "../common/html.js" ;
67import { icons , rankIcon } from "../common/icons.js" ;
78import { buildSearchFilter , clampValue } from "../common/ops.js" ;
89import { flexLayout , measureText } from "../common/render.js" ;
@@ -52,8 +53,10 @@ const LONG_LOCALES = [
5253/**
5354 * Create a stats card text item.
5455 *
56+ * The caller must ensure that the passed `icon` and `link` are properly sanitized!
57+ *
5558 * @param {object } params Object that contains the createTextNode parameters.
56- * @param {string } params.icon The icon to display.
59+ * @param {string } params.icon The sanitized icon to display.
5760 * @param {string } params.label The label to display.
5861 * @param {number } params.value The value to display.
5962 * @param {string } params.id The id of the stat.
@@ -64,7 +67,7 @@ const LONG_LOCALES = [
6467 * @param {boolean } params.bold Whether to bold the label.
6568 * @param {string } params.numberFormat The format of numbers on card.
6669 * @param {number= } params.numberPrecision The precision of numbers on card.
67- * @param {string } params.link Url to link to.
70+ * @param {string } params.link Sanitized url to link to.
6871 * @param {number } params.labelXOffset horizontal offset for label.
6972 * @returns {string } The stats card text item SVG object.
7073 */
@@ -83,6 +86,16 @@ const createTextNode = ({
8386 link,
8487 labelXOffset = 25 ,
8588} ) => {
89+ if ( ! Number . isFinite ( labelXOffset ) ) {
90+ throw new Error ( `Invalid labelXOffset: "${ labelXOffset } "` ) ;
91+ }
92+ if ( ! Number . isFinite ( shiftValuePos ) ) {
93+ throw new Error ( `Invalid shiftValuePos: "${ shiftValuePos } "` ) ;
94+ }
95+ if ( ! Number . isFinite ( index ) ) {
96+ throw new Error ( `Invalid index: "${ index } "` ) ;
97+ }
98+
8699 const precision =
87100 typeof numberPrecision === "number" && ! isNaN ( numberPrecision )
88101 ? clampValue ( numberPrecision , 0 , 2 )
@@ -109,7 +122,7 @@ const createTextNode = ({
109122 ${ iconSvg }
110123 <text class="stat ${
111124 bold ? " bold" : "not_bold"
112- } " ${ labelOffset } y="12.5">${ label } :</text>
125+ } " ${ labelOffset } y="12.5">${ encodeHTML ( label ) } :</text>
113126 <text
114127 class="stat ${ bold ? " bold" : "not_bold" } "
115128 x="${ ( showIcons ? 140 : 120 ) + ( bold ? 5 : 0 ) + shiftValuePos } "
@@ -422,13 +435,14 @@ const renderStatsCard = (
422435 }
423436
424437 let repoFilter = encodeURIComponent ( buildSearchFilter ( repo , owner ) ) ;
438+ const encodedUsername = encodeURIComponent ( username ) ;
425439 if ( show . includes ( "prs_authored" ) ) {
426440 STATS . prs_authored = {
427441 icon : icons . prs ,
428442 label : i18n . t ( "statcard.prs-authored" ) ,
429443 value : totalPRsAuthored ,
430444 id : "prs_authored" ,
431- link : `https://github.com/search?q=${ repoFilter } author%3A${ username } &type=pullrequests` ,
445+ link : `https://github.com/search?q=${ repoFilter } author%3A${ encodedUsername } &type=pullrequests` ,
432446 } ;
433447 }
434448 if ( show . includes ( "prs_commented" ) ) {
@@ -437,7 +451,7 @@ const renderStatsCard = (
437451 label : i18n . t ( "statcard.prs-commented" ) ,
438452 value : totalPRsCommented ,
439453 id : "prs_commented" ,
440- link : `https://github.com/search?q=${ repoFilter } commenter%3A${ username } +-author%3A${ username } &type=pullrequests` ,
454+ link : `https://github.com/search?q=${ repoFilter } commenter%3A${ encodedUsername } +-author%3A${ encodedUsername } &type=pullrequests` ,
441455 } ;
442456 }
443457 if ( show . includes ( "prs_reviewed" ) ) {
@@ -446,7 +460,7 @@ const renderStatsCard = (
446460 label : i18n . t ( "statcard.prs-reviewed" ) ,
447461 value : totalPRsReviewed ,
448462 id : "prs_reviewed" ,
449- link : `https://github.com/search?q=${ repoFilter } reviewed-by%3A${ username } +-author%3A${ username } &type=pullrequests` ,
463+ link : `https://github.com/search?q=${ repoFilter } reviewed-by%3A${ encodedUsername } +-author%3A${ encodedUsername } &type=pullrequests` ,
450464 } ;
451465 }
452466 if ( show . includes ( "issues_authored" ) ) {
@@ -455,7 +469,7 @@ const renderStatsCard = (
455469 label : i18n . t ( "statcard.issues-authored" ) ,
456470 value : totalIssuesAuthored ,
457471 id : "issues_authored" ,
458- link : `https://github.com/search?q=${ repoFilter } author%3A${ username } &type=issues` ,
472+ link : `https://github.com/search?q=${ repoFilter } author%3A${ encodedUsername } &type=issues` ,
459473 } ;
460474 }
461475 if ( show . includes ( "issues_commented" ) ) {
@@ -464,7 +478,7 @@ const renderStatsCard = (
464478 label : i18n . t ( "statcard.issues-commented" ) ,
465479 value : totalIssuesCommented ,
466480 id : "issues_commented" ,
467- link : `https://github.com/search?q=${ repoFilter } commenter%3A${ username } +-author%3A${ username } &type=issues` ,
481+ link : `https://github.com/search?q=${ repoFilter } commenter%3A${ encodedUsername } +-author%3A${ encodedUsername } &type=issues` ,
468482 } ;
469483 }
470484
0 commit comments