Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: escape double quote of url value in CSS url() #7718

Merged
12 changes: 12 additions & 0 deletions packages/core/integration-tests/test/css.js
Expand Up @@ -244,6 +244,18 @@ describe('css', () => {
);
});

it('should handle quote in CSS URL correctly', async function () {
await bundle(path.join(__dirname, '/integration/css-url-quote/index.css'));

let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8');

assert(
css.includes(
'url("data:image/svg+xml;utf8,with quote \\" and escape \\\\");',
),
);
});

it('should ignore url() with IE behavior specifiers', async function () {
let b = await bundle(
path.join(__dirname, '/integration/css-url-behavior/index.css'),
Expand Down
@@ -0,0 +1,5 @@
.quotes {
background-image: url('data:image/svg+xml;utf8,with quote " and escape \\');
width: 100px;
height: 100px;
}
@@ -0,0 +1,5 @@
require('./index.css');

module.exports = function () {
return 2;
};
10 changes: 8 additions & 2 deletions packages/core/utils/src/replaceBundleReferences.js
Expand Up @@ -33,13 +33,15 @@ export function replaceURLReferences({
bundleGraph,
contents,
map,
getReplacement,
relative = true,
}: {|
bundle: NamedBundle,
bundleGraph: BundleGraph<NamedBundle>,
contents: string,
relative?: boolean,
map?: ?SourceMap,
getReplacement: string => string,
mischnic marked this conversation as resolved.
Show resolved Hide resolved
|}): {|+contents: string, +map: ?SourceMap|} {
let replacements = new Map();
let urlDependencies = [];
Expand All @@ -61,7 +63,7 @@ export function replaceURLReferences({
if (resolved == null) {
replacements.set(placeholder, {
from: placeholder,
to: dependency.specifier,
to: getReplacement(dependency.specifier),
});
continue;
}
Expand All @@ -79,6 +81,7 @@ export function replaceURLReferences({
fromBundle: bundle,
toBundle: resolved,
relative,
getReplacement,
}),
);
}
Expand Down Expand Up @@ -156,11 +159,13 @@ export function getURLReplacement({
fromBundle,
toBundle,
relative,
getReplacement,
}: {|
dependency: Dependency,
fromBundle: NamedBundle,
toBundle: NamedBundle,
relative: boolean,
getReplacement?: string => string,
|}): {|from: string, to: string|} {
let to;

Expand Down Expand Up @@ -191,9 +196,10 @@ export function getURLReplacement({

let placeholder = dependency.meta?.placeholder ?? dependency.id;
invariant(typeof placeholder === 'string');

return {
from: placeholder,
to,
to: getReplacement ? getReplacement(to) : to,
};
}

Expand Down
7 changes: 6 additions & 1 deletion packages/packagers/css/src/CSSPackager.js
Expand Up @@ -129,6 +129,7 @@ export default (new Packager({
bundleGraph,
contents,
map,
getReplacement: escapeString,
}));

return replaceInlineReferences({
Expand All @@ -138,7 +139,7 @@ export default (new Packager({
getInlineBundleContents,
getInlineReplacement: (dep, inlineType, contents) => ({
from: getSpecifier(dep),
to: contents,
to: escapeString(contents),
}),
map,
});
Expand All @@ -153,6 +154,10 @@ export function getSpecifier(dep: Dependency): string {
return dep.id;
}

function escapeString(contents: string): string {
return contents.replace(/(["\\])/g, '\\$1');
}

async function processCSSModule(
options,
logger,
Expand Down
1 change: 1 addition & 0 deletions packages/packagers/html/src/HTMLPackager.js
Expand Up @@ -74,6 +74,7 @@ export default (new Packager({
bundleGraph,
contents: html,
relative: false,
getReplacement: contents => contents.replace(/"/g, '&quot;'),
});

return replaceInlineReferences({
Expand Down
1 change: 1 addition & 0 deletions packages/packagers/js/src/index.js
Expand Up @@ -72,6 +72,7 @@ export default (new Packager({
bundleGraph,
contents,
map,
getReplacement: s => JSON.stringify(s).slice(1, -1),
}));
}

Expand Down
1 change: 1 addition & 0 deletions packages/packagers/raw-url/src/RawUrlPackager.js
Expand Up @@ -17,6 +17,7 @@ export default (new Packager({
bundleGraph,
contents: await assets[0].getCode(),
relative: false,
getReplacement: s => s,
});
return {contents};
},
Expand Down
1 change: 1 addition & 0 deletions packages/packagers/svg/src/SVGPackager.js
Expand Up @@ -58,6 +58,7 @@ export default (new Packager({
bundleGraph,
contents: svg,
relative: false,
getReplacement: contents => contents.replace(/"/g, '&quot;'),
});

return replaceInlineReferences({
Expand Down
1 change: 1 addition & 0 deletions packages/packagers/xml/src/XMLPackager.js
Expand Up @@ -68,6 +68,7 @@ export default (new Packager({
bundleGraph,
contents: code,
relative: false,
getReplacement: contents => contents.replace(/"/g, '&quot;'),
});

return replaceInlineReferences({
Expand Down
14 changes: 9 additions & 5 deletions packages/transformers/css/src/CSSTransformer.js
Expand Up @@ -83,12 +83,16 @@ export default (new Transformer({
symbols: new Map([['*', {local: '*', isWeak: true, loc}]]),
});
} else if (dep.type === 'url') {
asset.addURLDependency(dep.url, {
loc,
meta: {
placeholder: dep.placeholder,
asset.addURLDependency(
// when URL is escaped, parcel resolver cannot resolve URL, so we need to unescape it in transformer
dep.url.replace(/\\"/g, '"').replace(/\\'/g, "'"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. The url will already be unescaped before it is passed to us.

{
loc,
meta: {
placeholder: dep.placeholder,
},
},
});
);
}
}
}
Expand Down