-
-
Notifications
You must be signed in to change notification settings - Fork 606
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: proper URL escaping and wrapping (url()
)
#627
Changes from 4 commits
d14e8b8
2db8469
74431ba
fdac0b0
1ac7661
87caf33
d34841f
f144280
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module.exports = function(url) { | ||
// If url is already wrapped in quotes, remove them | ||
if (/^['"].*['"]$/.test(url)) { | ||
url = url.slice(1, -1); | ||
} | ||
// Should url be wrapped? | ||
// See https://drafts.csswg.org/css-values-3/#urls | ||
if (/["'() \t\n]/.test(url)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What exactly is tested here, could you give a few examples please ?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are the characters outlined as requiring escaping in unquoted URLs in the CSS spec. https://drafts.csswg.org/css-values-3/#urls If any of them are present, the string is wrapped in quotes and quotes/newline is escaped. Like with Some examples that would require either escaping or wrapping:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding your second point: Why would it be better to test with a global regex? I'm not aware of any difference caused by this. |
||
return '"' + url.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"' | ||
} | ||
|
||
return url | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,8 +82,13 @@ module.exports = function(content, map) { | |
"[" + JSON.stringify(importItem.export) + "] + \""; | ||
} | ||
|
||
var escapeUrlHelper; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. // url() helper to escape quotes
var escape = ""
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The variable is now named |
||
cssAsString = cssAsString.replace(result.importItemRegExpG, importItemMatcher.bind(this)); | ||
if(query.url !== false) { | ||
if(query.url !== false && result.urlItems.length > 0) { | ||
escapeUrlHelper = "var escapeUrl = require(" + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. escape = "require(" + loaderUtils.stringifyRequest(this, require.resolve("./url/escape.js")) + ")\n" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused here. Surely you mean Don't you think it gets a little confusing when you call both the variable in the loader and in the string by the same name? |
||
loaderUtils.stringifyRequest(this, require.resolve("./escape-url.js")) | ||
+ ")\n" | ||
|
||
cssAsString = cssAsString.replace(result.urlItemRegExpG, function(item) { | ||
var match = result.urlItemRegExp.exec(item); | ||
var idx = +match[1]; | ||
|
@@ -95,15 +100,16 @@ module.exports = function(content, map) { | |
if(idx > 0) { // idx === 0 is catched by isUrlRequest | ||
// in cases like url('webfont.eot?#iefix') | ||
urlRequest = url.substr(0, idx); | ||
return "\" + require(" + loaderUtils.stringifyRequest(this, urlRequest) + ") + \"" + | ||
return "\" + escapeUrl(require(" + loaderUtils.stringifyRequest(this, urlRequest) + ")) + \"" + | ||
url.substr(idx); | ||
} | ||
urlRequest = url; | ||
return "\" + require(" + loaderUtils.stringifyRequest(this, urlRequest) + ") + \""; | ||
return "\" + escapeUrl(require(" + loaderUtils.stringifyRequest(this, urlRequest) + ")) + \""; | ||
}.bind(this)); | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
escapeUrlHelper = "" | ||
} | ||
|
||
|
||
|
||
var exportJs = compileExports(result, importItemMatcher.bind(this), camelCaseKeys); | ||
if (exportJs) { | ||
exportJs = "exports.locals = " + exportJs + ";"; | ||
|
@@ -127,7 +133,8 @@ module.exports = function(content, map) { | |
} | ||
|
||
// embed runtime | ||
callback(null, "exports = module.exports = require(" + | ||
callback(null, escapeUrlHelper + | ||
"exports = module.exports = require(" + | ||
loaderUtils.stringifyRequest(this, require.resolve("./css-base.js")) + | ||
")(" + query.sourceMap + ");\n" + | ||
"// imports\n" + | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
background: url({./module}); | ||
background: url({./module}); | ||
background: url("{./module module}"); | ||
background: url('{./module module}'); | ||
background: url("{./module module}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why we change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In principle it could be done. It will complicate things quite a bit, though, since the whole point of the update is to quote based on module-content instead of name. The type of quotes would have to be passed to the client (something like I skipped it, since I can't really imagine a scenario where it would be important to use single quotes. If this is implemented, there should maybe also be an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, but "break something" here means "result in a slightly longer string since double-quotes are escaped". The whole point of this change is to make it impossible for other loaders to break your CSS. My arguments for using only double-quotes summed up:
Of course it is not my package, and I'm not that experienced with writing loaders, so I defer entirely to your judgements. If I have failed to convince you, I'll change it so there is a separate escape-function for single-quotes. If that is the case, should I also make an unquoted escape-function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm no debating That has nothing to do with not merging it, just saying it's going to happen & we now we have context as to why. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to keep in mind that Anything that lands in this repo has to be vetted, pondered, picked apart & poked so we at the very least can have a reason why there are 4 new issues with version Changes in behaviors get that & the rubber glove treatment, it's just too easy to not account for a path in this vast ecosystem where Change X has an unintended side effect on Y & people show up with torches & pitch forks. |
||
background: url({./module}); | ||
background: url({./module}#?iefix); | ||
background: url("#hash"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ describe("url", function() { | |
[1, ".class { background: green url(\"{./img img.png}\") xyz }", ""] | ||
]); | ||
test("background 2 img contain space in name", ".class { background: green url( 'img img.png' ) xyz }", [ | ||
[1, ".class { background: green url('{./img img.png}') xyz }", ""] | ||
[1, ".class { background: green url(\"{./img img.png}\") xyz }", ""] | ||
]); | ||
test("background img absolute", ".class { background: green url(/img.png) xyz }", [ | ||
[1, ".class { background: green url(/img.png) xyz }", ""] | ||
|
@@ -103,6 +103,25 @@ describe("url", function() { | |
test("external schema-less url", ".class { background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz }", [ | ||
[1, ".class { background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz }", ""] | ||
]); | ||
|
||
test("module wrapped in spaces", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(module-wrapped) xyz }", ""] | ||
], "", { './module': "\"module-wrapped\"" }); | ||
test("module with space", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(\"module with space\") xyz }", ""] | ||
], "", { './module': "module with space" }); | ||
test("module with quote", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(\"module\\\"with\\\"quote\") xyz }", ""] | ||
], "", { './module': "module\"with\"quote" }); | ||
test("module with quote wrapped", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(\"module\\\"with\\\"quote\\\"wrapped\") xyz }", ""] | ||
], "", { './module': "\"module\"with\"quote\"wrapped\"" }); | ||
test("module with parens", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(\"module(with-parens)\") xyz }", ""] | ||
], "", { './module': 'module(with-parens)' }); | ||
test("module with newline", ".class { background: green url(module) xyz }", [ | ||
[1, ".class { background: green url(\"module\\nwith\\nnewline\") xyz }", ""] | ||
], "", { './module': "module\nwith\nnewline" }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test added of one of the url-loader outputs from this article. It's a very small file, but as far as I know all base64-encoded data-URIs are valid in CSS when unquoted. If you have an example in mind of something that might fail I'll add that too though. |
||
|
||
test("background img with url", ".class { background: green url( \"img.png\" ) xyz }", [ | ||
[1, ".class { background: green url( \"img.png\" ) xyz }", ""] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/escape-url-js
=>lib/url/escape.js
module.exports = function escape (url) {
So we hopefully can require the named
{Function}
without having to decl a new variable later :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.