Escape HTML by default (#2747)
* feat: escape HTML by default * feat(test): add escaping test
This commit is contained in:
parent
658a92915d
commit
05b66bd68b
7 changed files with 35 additions and 26 deletions
5
.changeset/sour-socks-enjoy.md
Normal file
5
.changeset/sour-socks-enjoy.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Escape HTML inside of expressions by default. Please see [our migration guide](https://docs.astro.build/en/migrate/#deprecated-unescaped-html) for more details.
|
|
@ -58,7 +58,7 @@
|
|||
"test:match": "mocha --timeout 20000 -g"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^0.12.0-next.8",
|
||||
"@astrojs/compiler": "^0.12.0-next.9",
|
||||
"@astrojs/language-server": "^0.8.6",
|
||||
"@astrojs/markdown-remark": "^0.6.4",
|
||||
"@astrojs/prism": "0.4.0",
|
||||
|
@ -95,10 +95,10 @@
|
|||
"resolve": "^1.20.0",
|
||||
"rollup": "^2.64.0",
|
||||
"semver": "^7.3.5",
|
||||
"sirv": "^2.0.2",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"shiki": "^0.10.0",
|
||||
"shorthash": "^0.0.2",
|
||||
"sirv": "^2.0.2",
|
||||
"slash": "^4.0.0",
|
||||
"sourcemap-codec": "^1.4.8",
|
||||
"srcset-parse": "^1.1.0",
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
const entities = { '"': 'quot', '&': 'amp', "'": 'apos', '<': 'lt', '>': 'gt' } as const;
|
||||
|
||||
const warned = new Set<string>();
|
||||
export const escapeHTML = (string: any, { deprecated = false }: { deprecated?: boolean } = {}) => {
|
||||
const escaped = string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';');
|
||||
if (!deprecated) return escaped;
|
||||
if (warned.has(string) || !string.match(/[&<>]/g)) return string;
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Unescaped HTML content found inside expression!
|
||||
|
||||
The next minor version of Astro will automatically escape all
|
||||
expression content. Please use the \`set:html\` directive.
|
||||
|
||||
Expression content:
|
||||
${string}`);
|
||||
warned.add(string);
|
||||
|
||||
// Return unescaped content for now. To be removed.
|
||||
return string;
|
||||
};
|
||||
export const escapeHTML = (string: any) => string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';');
|
||||
|
||||
/**
|
||||
* RawString is a "blessed" version of String
|
||||
|
|
|
@ -8,7 +8,7 @@ import { escapeHTML, UnescapedString, unescapeHTML } from './escape.js';
|
|||
|
||||
export type { Metadata } from './metadata';
|
||||
export { createMetadata } from './metadata.js';
|
||||
export { escapeHTML, unescapeHTML } from './escape.js';
|
||||
export { unescapeHTML } from './escape.js';
|
||||
|
||||
const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
||||
const htmlBooleanAttributes =
|
||||
|
@ -36,7 +36,7 @@ async function _render(child: any): Promise<any> {
|
|||
// of wrapping it in a function and calling it.
|
||||
return _render(child());
|
||||
} else if (typeof child === 'string') {
|
||||
return escapeHTML(child, { deprecated: true });
|
||||
return escapeHTML(child);
|
||||
} else if (!child && child !== 0) {
|
||||
// do nothing, safe to ignore falsey values.
|
||||
}
|
||||
|
|
|
@ -99,4 +99,13 @@ describe('Expressions', () => {
|
|||
// test 9: Expected {undefined && <span id="undefined" />} not to render
|
||||
expect($('#frag-undefined')).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('Escapes HTML by default', async () => {
|
||||
const html = await fixture.readFile('/escape/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('body').children()).to.have.lengthOf(1);
|
||||
expect($('body').text()).to.include('<script>console.log("pwnd")</script>')
|
||||
expect($('#trusted')).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
|
9
packages/astro/test/fixtures/astro-expr/src/pages/escape.astro
vendored
Normal file
9
packages/astro/test/fixtures/astro-expr/src/pages/escape.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<title>My site</title>
|
||||
</head>
|
||||
<body>
|
||||
{'<script>console.log("pwnd")</script>'}
|
||||
<Fragment set:html={'<script id="trusted">console.log("yay!")</script>'} />
|
||||
</body>
|
||||
</html>
|
|
@ -340,7 +340,7 @@ importers:
|
|||
|
||||
packages/astro:
|
||||
specifiers:
|
||||
'@astrojs/compiler': ^0.12.0-next.8
|
||||
'@astrojs/compiler': ^0.12.0-next.9
|
||||
'@astrojs/language-server': ^0.8.6
|
||||
'@astrojs/markdown-remark': ^0.6.4
|
||||
'@astrojs/parser': ^0.22.1
|
||||
|
@ -414,7 +414,7 @@ importers:
|
|||
yargs-parser: ^21.0.0
|
||||
zod: ^3.8.1
|
||||
dependencies:
|
||||
'@astrojs/compiler': 0.12.0-next.8
|
||||
'@astrojs/compiler': 0.12.0-next.9
|
||||
'@astrojs/language-server': 0.8.10
|
||||
'@astrojs/markdown-remark': link:../markdown/remark
|
||||
'@astrojs/prism': link:../astro-prism
|
||||
|
@ -1328,10 +1328,12 @@ packages:
|
|||
leven: 3.1.0
|
||||
dev: true
|
||||
|
||||
/@astrojs/compiler/0.12.0-next.8:
|
||||
resolution: {integrity: sha512-HeREaw5OR5J7zML+/LxhrqUr57571kyNXL4HD2pU929oevhx3PQ37PQ0FkD5N65X9YfO+gcoEO6whl76vtSZag==}
|
||||
/@astrojs/compiler/0.12.0-next.9:
|
||||
resolution: {integrity: sha512-XHvGrPBhr/LBYZT4TuXf8mK2+CYCClQoPln9FlF6gIx4yTsWpUwAi3mhhFOGJi4NB1Y1ZbcXS60IjMy/2adpLg==}
|
||||
dependencies:
|
||||
tsm: 2.2.1
|
||||
typescript: 4.6.2
|
||||
uvu: 0.5.3
|
||||
dev: false
|
||||
|
||||
/@astrojs/language-server/0.8.10:
|
||||
|
@ -8788,6 +8790,7 @@ packages:
|
|||
/source-map/0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
|
||||
/source-map/0.7.3:
|
||||
resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==}
|
||||
|
|
Loading…
Reference in a new issue