mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Fix parsing url(…) with special characters such as ; or {} (#14879)
This PR fixes an issue where some special characters (with an actual
meaning CSS) were used inside of the `url(…)` function, would result in
incorrectly parsed CSS.
For example, when we encounter a `{`, then we would start a new "block"
for nesting purposes. If we encounter an `}`, then the block would end.
If we encounter a `;`, then that would be the end of a declaration.
All of that is true, unless we are in a `url(…)` function. In that case,
we should ignore all of those characters and treat them as part of the
URL.
This is only an issue because:
1. We are allowed to use these characters in URLs.
2. We can write an url inside `url(…)` without quotes. With quotes, this
would not be an issue.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This commit is contained in:
parent
8bd3c85420
commit
4e164107dd
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix crash when using `@source` containing `..` ([#14831](https://github.com/tailwindlabs/tailwindcss/pull/14831))
|
||||
- Ensure instances of the same variant with different values are always sorted deterministically (e.g. `data-focus:flex` and `data-active:flex`) ([#14835](https://github.com/tailwindlabs/tailwindcss/pull/14835))
|
||||
- Ensure `--inset-ring=*` and `--inset-shadow-*` variables are ignored by `inset-*` utilities ([#14855](https://github.com/tailwindlabs/tailwindcss/pull/14855))
|
||||
- Ensure `url(…)` containing special characters such as `;` or `{}` end up in one declaration ([#14879](https://github.com/tailwindlabs/tailwindcss/pull/14879))
|
||||
- _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830))
|
||||
- _Upgrade (experimental)_: Remove whitespace around `,` separator when print arbitrary values ([#14838](https://github.com/tailwindlabs/tailwindcss/pull/14838))
|
||||
|
||||
|
||||
@ -505,6 +505,59 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse url(…) without quotes and special characters such as `;`, `{}`, and `[]`', () => {
|
||||
expect(
|
||||
parse(css`
|
||||
.foo {
|
||||
/* ';' should be valid inside the 'url(…)' function */
|
||||
background: url(data:image/png;base64,abc==);
|
||||
|
||||
/* '{', '}', '[' and ']' should be valid inside the 'url(…)' function */
|
||||
/* '{' and '}' should not start a new block (nesting) */
|
||||
background: url(https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]);
|
||||
|
||||
/* '{' and '}' don't need to be balanced */
|
||||
background: url(https://example-image-search.org?curlies=}});
|
||||
|
||||
/* '(' and ')' are not valid, unless we are in a string with quotes */
|
||||
background: url('https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]&format=(png|jpg)');
|
||||
}
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
kind: 'rule',
|
||||
selector: '.foo',
|
||||
nodes: [
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: 'background',
|
||||
value: 'url(data:image/png;base64,abc==)',
|
||||
important: false,
|
||||
},
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: 'background',
|
||||
value: 'url(https://example-image-search.org?q={query;limit=5}&ids=[1,2,3])',
|
||||
important: false,
|
||||
},
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: 'background',
|
||||
value: 'url(https://example-image-search.org?curlies=}})',
|
||||
important: false,
|
||||
},
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: 'background',
|
||||
value:
|
||||
"url('https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]&format=(png|jpg)')",
|
||||
important: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectors', () => {
|
||||
|
||||
@ -331,7 +331,10 @@ export function parse(input: string) {
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
else if (currentChar === SEMICOLON) {
|
||||
else if (
|
||||
currentChar === SEMICOLON &&
|
||||
closingBracketStack[closingBracketStack.length - 1] !== ')'
|
||||
) {
|
||||
let declaration = parseDeclaration(buffer)
|
||||
if (parent) {
|
||||
parent.nodes.push(declaration)
|
||||
@ -343,7 +346,10 @@ export function parse(input: string) {
|
||||
}
|
||||
|
||||
// Start of a block.
|
||||
else if (currentChar === OPEN_CURLY) {
|
||||
else if (
|
||||
currentChar === OPEN_CURLY &&
|
||||
closingBracketStack[closingBracketStack.length - 1] !== ')'
|
||||
) {
|
||||
closingBracketStack += '}'
|
||||
|
||||
// At this point `buffer` should resemble a selector or an at-rule.
|
||||
@ -368,7 +374,10 @@ export function parse(input: string) {
|
||||
}
|
||||
|
||||
// End of a block.
|
||||
else if (currentChar === CLOSE_CURLY) {
|
||||
else if (
|
||||
currentChar === CLOSE_CURLY &&
|
||||
closingBracketStack[closingBracketStack.length - 1] !== ')'
|
||||
) {
|
||||
if (closingBracketStack === '') {
|
||||
throw new Error('Missing opening {')
|
||||
}
|
||||
@ -456,6 +465,22 @@ export function parse(input: string) {
|
||||
node = null
|
||||
}
|
||||
|
||||
// `(`
|
||||
else if (currentChar === OPEN_PAREN) {
|
||||
closingBracketStack += ')'
|
||||
buffer += '('
|
||||
}
|
||||
|
||||
// `)`
|
||||
else if (currentChar === CLOSE_PAREN) {
|
||||
if (closingBracketStack[closingBracketStack.length - 1] !== ')') {
|
||||
throw new Error('Missing opening (')
|
||||
}
|
||||
|
||||
closingBracketStack = closingBracketStack.slice(0, -1)
|
||||
buffer += ')'
|
||||
}
|
||||
|
||||
// Any other character is part of the current node.
|
||||
else {
|
||||
// Skip whitespace at the start of a new node.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user