Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-schools-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes `&` characters appearing as raw entity strings (e.g. `&#38;`) in `<meta>` tags when viewed in link previews or raw HTML.
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const toIdent = (k: string) =>

export const toAttributeString = (value: any, shouldEscape = true) =>
shouldEscape
? String(value).replace(AMPERSAND_REGEX, '&#38;').replace(DOUBLE_QUOTE_REGEX, '&#34;')
? String(value).replace(AMPERSAND_REGEX, '&amp;').replace(DOUBLE_QUOTE_REGEX, '&quot;')
: value;

const kebab = (k: string) =>
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/units/app/astro-attrs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,13 @@ describe('Attributes', async () => {

// cheerio will unescape the values, so checking that the url rendered escaped has to be done manually
assert.equal(
html.includes('https://example.com/api/og?title=hello&#38;description=somedescription'),
html.includes('https://example.com/api/og?title=hello&amp;description=somedescription'),
true,
);

// cheerio will unescape the values, so checking that the url rendered unescaped to begin with has to be done manually
assert.equal(
html.includes('cmd: echo &#34;foo&#34; &#38;&#38; echo &#34;bar&#34; > /tmp/hello.txt'),
html.includes('cmd: echo &quot;foo&quot; &amp;&amp; echo &quot;bar&quot; > /tmp/hello.txt'),
true,
);

Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/units/app/url-attribute-xss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('addAttribute URL XSS', () => {
const result = String(addAttribute(maliciousUrl, 'href'));

// The " must be escaped so the value stays inside a single attribute
assert.ok(result.includes('&#34;'), `double quotes should be escaped, got: ${result}`);
assert.ok(result.includes('&quot;'), `double quotes should be escaped, got: ${result}`);
assert.match(
result,
/^\s+href="[^"]*"$/,
Expand All @@ -20,7 +20,7 @@ describe('addAttribute URL XSS', () => {
const url = 'https://example.com/?a=1&b=2&c=3';
const result = String(addAttribute(url, 'href'));

assert.ok(result.includes('&#38;'), `ampersands should be escaped, got: ${result}`);
assert.ok(result.includes('&amp;'), `ampersands should be escaped, got: ${result}`);
assert.match(
result,
/^\s+href="[^"]*"$/,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe('style object via addAttribute', () => {

it('handles url() with quotes in style object', () => {
const result = addAttribute({ backgroundImage: 'url("a")' }, 'style');
assert.equal(result.toString(), ' style="background-image:url(&#34;a&#34;)"');
assert.equal(result.toString(), ' style="background-image:url(&quot;a&quot;)"');
});

it('passes through string style values unchanged', () => {
Expand Down
10 changes: 5 additions & 5 deletions packages/astro/test/units/render/html-primitives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import {
import { createTestApp, createPage } from '../mocks.ts';

describe('toAttributeString', () => {
it('escapes & to &#38;', () => {
assert.equal(toAttributeString('a&b'), 'a&#38;b');
it('escapes & to &amp;', () => {
assert.equal(toAttributeString('a&b'), 'a&amp;b');
});

it('escapes " to &#34;', () => {
assert.equal(toAttributeString('say "hello"'), 'say &#34;hello&#34;');
it('escapes " to &quot;', () => {
assert.equal(toAttributeString('say "hello"'), 'say &quot;hello&quot;');
});

it('escapes both & and " in the same string', () => {
assert.equal(toAttributeString('"a&b"'), '&#34;a&#38;b&#34;');
assert.equal(toAttributeString('"a&b"'), '&quot;a&amp;b&quot;');
});

it('passes through normal strings unchanged', () => {
Expand Down
Loading