summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/quick-islands-ring.md5
-rw-r--r--packages/astro/src/runtime/server/render/util.ts14
-rw-r--r--packages/astro/test/astro-attrs.test.js3
-rw-r--r--packages/astro/test/fixtures/astro-attrs/src/pages/index.astro1
4 files changed, 23 insertions, 0 deletions
diff --git a/.changeset/quick-islands-ring.md b/.changeset/quick-islands-ring.md
new file mode 100644
index 000000000..a3b3e14ca
--- /dev/null
+++ b/.changeset/quick-islands-ring.md
@@ -0,0 +1,5 @@
+---
+"astro": patch
+---
+
+Prevents fully formed URLs in attributes from being escaped
diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts
index 3fa01aeb6..52149a031 100644
--- a/packages/astro/src/runtime/server/render/util.ts
+++ b/packages/astro/src/runtime/server/render/util.ts
@@ -104,6 +104,11 @@ Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the
return markHTMLString(` class="${toAttributeString(value, shouldEscape)}"`);
}
+ // Prevents URLs in attributes from being escaped in static builds
+ if (typeof value === 'string' && value.includes('&') && urlCanParse(value)) {
+ return markHTMLString(` ${key}="${toAttributeString(value, false)}"`);
+ }
+
// Boolean values only need the key
if (value === true && (key.startsWith('data-') || htmlBooleanAttributes.test(key))) {
return markHTMLString(` ${key}`);
@@ -224,3 +229,12 @@ export function promiseWithResolvers<T = any>(): PromiseWithResolvers<T> {
reject,
};
}
+
+function urlCanParse(url: string) {
+ try {
+ new URL(url);
+ return true;
+ } catch {
+ return false;
+ }
+}
diff --git a/packages/astro/test/astro-attrs.test.js b/packages/astro/test/astro-attrs.test.js
index 0866e4828..4ee547d91 100644
--- a/packages/astro/test/astro-attrs.test.js
+++ b/packages/astro/test/astro-attrs.test.js
@@ -31,6 +31,9 @@ describe('Attributes', async () => {
'html-enum-false': { attribute: 'draggable', value: 'false' },
};
+ // cheerio will unescape the values, so checking that the url rendered unescaped to begin with has to be done manually
+ assert.equal(html.includes("https://example.com/api/og?title=hello&description=somedescription"), true);
+
for (const id of Object.keys(attrs)) {
const { attribute, value } = attrs[id];
const attr = $(`#${id}`).attr(attribute);
diff --git a/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro b/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro
index 82e03a3ae..b7e617e36 100644
--- a/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro
+++ b/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro
@@ -5,6 +5,7 @@
<span id="empty" attr="" />
<span id="null" attr={null} />
<span id="undefined" attr={undefined} />
+<span id="url" attr={"https://example.com/api/og?title=hello&description=somedescription"}/>
<!--
Per HTML spec, some attributes should be treated as booleans
These should always render <span async /> or <span /> (without a string value)