Skip to content

Commit 1d4ad5a

Browse files
authored
Improve html escape (go-gitea#34911)
drop "escape-goat"
1 parent 35f0b5a commit 1d4ad5a

25 files changed

+103
-68
lines changed

.eslintrc.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ module.exports = {
9191
plugins: ['@vitest/eslint-plugin'],
9292
globals: vitestPlugin.environments.env.globals,
9393
rules: {
94+
'github/unescaped-html-literal': [0],
9495
'@vitest/consistent-test-filename': [0],
9596
'@vitest/consistent-test-it': [0],
9697
'@vitest/expect-expect': [0],
@@ -423,7 +424,7 @@ module.exports = {
423424
'github/no-useless-passive': [2],
424425
'github/prefer-observers': [2],
425426
'github/require-passive-events': [2],
426-
'github/unescaped-html-literal': [0],
427+
'github/unescaped-html-literal': [2],
427428
'grouped-accessor-pairs': [2],
428429
'guard-for-in': [0],
429430
'id-blacklist': [0],

package-lock.json

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"dropzone": "6.0.0-beta.2",
2828
"easymde": "2.20.0",
2929
"esbuild-loader": "4.3.0",
30-
"escape-goat": "4.0.0",
3130
"fast-glob": "3.3.3",
3231
"htmx.org": "2.0.6",
3332
"idiomorph": "0.7.3",

web_src/js/bootstrap.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// to make sure the error handler always works, we should never import `window.config`, because
33
// some user's custom template breaks it.
44
import type {Intent} from './types.ts';
5+
import {html} from './utils/html.ts';
56

67
// This sets up the URL prefix used in webpack's chunk loading.
78
// This file must be imported before any lazy-loading is being attempted.
@@ -23,7 +24,7 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
2324
let msgDiv = msgContainer.querySelector<HTMLDivElement>(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
2425
if (!msgDiv) {
2526
const el = document.createElement('div');
26-
el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
27+
el.innerHTML = html`<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
2728
msgDiv = el.childNodes[0] as HTMLDivElement;
2829
}
2930
// merge duplicated messages into "the message (count)" format

web_src/js/components/ViewFileTreeStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {reactive} from 'vue';
22
import {GET} from '../modules/fetch.ts';
33
import {pathEscapeSegments} from '../utils/url.ts';
44
import {createElementFromHTML} from '../utils/dom.ts';
5+
import {html} from '../utils/html.ts';
56

67
export function createViewFileTreeStore(props: { repoLink: string, treePath: string, currentRefNameSubURL: string}) {
78
const store = reactive({
@@ -16,7 +17,7 @@ export function createViewFileTreeStore(props: { repoLink: string, treePath: str
1617
if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent);
1718
}
1819
if (poolSvgs.length) {
19-
const svgContainer = createElementFromHTML('<div class="global-svg-icon-pool tw-hidden"></div>');
20+
const svgContainer = createElementFromHTML(html`<div class="global-svg-icon-pool tw-hidden"></div>`);
2021
svgContainer.innerHTML = poolSvgs.join('');
2122
document.body.append(svgContainer);
2223
}

web_src/js/features/comp/ConfirmModal.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {svg} from '../../svg.ts';
2-
import {htmlEscape} from 'escape-goat';
2+
import {html, htmlRaw} from '../../utils/html.ts';
33
import {createElementFromHTML} from '../../utils/dom.ts';
44
import {fomanticQuery} from '../../modules/fomantic/base.ts';
55

@@ -12,17 +12,17 @@ type ConfirmModalOptions = {
1212
}
1313

1414
export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement {
15-
const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : '';
16-
return createElementFromHTML(`
17-
<div class="ui g-modal-confirm modal">
18-
${headerHtml}
19-
<div class="content">${htmlEscape(content)}</div>
20-
<div class="actions">
21-
<button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button>
22-
<button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button>
23-
</div>
24-
</div>
25-
`);
15+
const headerHtml = header ? html`<div class="header">${header}</div>` : '';
16+
return createElementFromHTML(html`
17+
<div class="ui g-modal-confirm modal">
18+
${htmlRaw(headerHtml)}
19+
<div class="content">${content}</div>
20+
<div class="actions">
21+
<button class="ui cancel button">${htmlRaw(svg('octicon-x'))} ${i18n.modal_cancel}</button>
22+
<button class="ui ${confirmButtonColor} ok button">${htmlRaw(svg('octicon-check'))} ${i18n.modal_confirm}</button>
23+
</div>
24+
</div>
25+
`.trim());
2626
}
2727

2828
export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> {

web_src/js/features/comp/EditorUpload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, drop
114114

115115
export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) {
116116
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), '');
117-
text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
117+
text = text.replace(new RegExp(`[<]img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
118118
return text;
119119
}
120120

web_src/js/features/comp/SearchUserBox.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {htmlEscape} from 'escape-goat';
1+
import {htmlEscape} from '../../utils/html.ts';
22
import {fomanticQuery} from '../../modules/fomantic/base.ts';
33

44
const {appSubUrl} = window.config;

web_src/js/features/dropzone.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {svg} from '../svg.ts';
2-
import {htmlEscape} from 'escape-goat';
2+
import {html} from '../utils/html.ts';
33
import {clippie} from 'clippie';
44
import {showTemporaryTooltip} from '../modules/tippy.ts';
55
import {GET, POST} from '../modules/fetch.ts';
@@ -33,14 +33,14 @@ export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFi
3333
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
3434
// method to change image size in Markdown that is supported by all implementations.
3535
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
36-
fileMarkdown = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(file.name)}" src="attachments/${htmlEscape(file.uuid)}">`;
36+
fileMarkdown = html`<img width="${Math.round(width / dppx)}" alt="${file.name}" src="attachments/${file.uuid}">`;
3737
} else {
3838
// Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}"
3939
// TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments"
4040
fileMarkdown = `![${file.name}](/attachments/${file.uuid})`;
4141
}
4242
} else if (isVideoFile(file)) {
43-
fileMarkdown = `<video src="attachments/${htmlEscape(file.uuid)}" title="${htmlEscape(file.name)}" controls></video>`;
43+
fileMarkdown = html`<video src="attachments/${file.uuid}" title="${file.name}" controls></video>`;
4444
}
4545
return fileMarkdown;
4646
}

web_src/js/features/emoji.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import emojis from '../../../assets/emoji.json' with {type: 'json'};
2+
import {html} from '../utils/html.ts';
23

34
const {assetUrlPrefix, customEmojis} = window.config;
45

@@ -24,12 +25,11 @@ for (const key of emojiKeys) {
2425
export function emojiHTML(name: string) {
2526
let inner;
2627
if (Object.hasOwn(customEmojis, name)) {
27-
inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
28+
inner = html`<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
2829
} else {
2930
inner = emojiString(name);
3031
}
31-
32-
return `<span class="emoji" title=":${name}:">${inner}</span>`;
32+
return html`<span class="emoji" title=":${name}:">${inner}</span>`;
3333
}
3434

3535
// retrieve string for given emoji name

0 commit comments

Comments
 (0)