-
-
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
Conversation
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.
Before I review this further does JSON.stringify(url)
eventually do the trick here ?
lib/escape-url.js
Outdated
@@ -0,0 +1,13 @@ | |||
module.exports = function(url) { | |||
// If url is already wrapped in quotes, remove them | |||
if ((url[0] === '"' || url[0] === '\'') && url[0] === url[url.length - 1]) { |
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.
if (/^['"].*['"]$/.test(url)) {
url = url.slice(1, -1);
}
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.
const url1 = 'path/to/image.png'
const url2 = '"path/to/image.png"'
console.log(JSON.stringify(url1))
console.log(JSON.stringify(url2))
|
Codecov Report
@@ Coverage Diff @@
## master #627 +/- ##
==========================================
+ Coverage 98.92% 98.94% +0.02%
==========================================
Files 10 11 +1
Lines 371 379 +8
Branches 87 88 +1
==========================================
+ Hits 367 375 +8
Misses 4 4
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #627 +/- ##
==========================================
+ Coverage 98.92% 98.94% +0.02%
==========================================
Files 10 11 +1
Lines 371 379 +8
Branches 87 88 +1
==========================================
+ Hits 367 375 +8
Misses 4 4
Continue to review full report at Codecov.
|
Could you check if return "JSON.stringify(require(" + loaderUtils.stringifyRequest(this, urlRequest) + "))"; also works ? Not 💯 if I placed it correctly :) |
I can't find a reason Two things to note: if you replace the entire Your call :) |
lib/escape-url.js
Outdated
} | ||
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly is tested here, could you give a few examples please ?
["'()=> <=\t\n]
=>\s
?/["'() \t\n]/.test(url)
=>/["'() \t\n]/g.test(url)
(/RegExp/g
) ?
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.
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 JSON.stringify
, yes, it's basically the same as \s
in regex, just from different standards. There seems to be some rarely used characters regex considers whitespace that CSS doesn't, but again, it is unlikely to matter. I just prefer to be specific when the standard is right there.
Some examples that would require either escaping or wrapping:
url(open(parens)
=> url(open\(parens)
/ url("open(parens")
url(close)parens)
=> url(close\)parens)
/ url("close)parens")
url(quote"in"middle)
=> url("quote\"in\"middle")
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.
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.
lib/loader.js
Outdated
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
// url() helper to escape quotes
var escape = ""
\n
after the variable decl please
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.
The variable is now named urlEscapeHelper
in the loader and escape
in the output. If you feel that is incorrect could you be a bit more specific about the naming you would prefer?
lib/loader.js
Outdated
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused here. Surely you mean escape = "var escape = require(" + ...
, right?
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?
lib/loader.js
Outdated
}.bind(this)); | ||
} else { |
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.
rm the else
as initializing escape
with a default value (""
) should be enough
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.
], "", { './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 comment
The reason will be displayed to describe this comment to others. Learn more.
We need to test url-loader
output here aswell
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.
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.
lib/escape-url.js
Outdated
@@ -0,0 +1,13 @@ | |||
module.exports = function(url) { |
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.
What about data URL's, e.g from the |
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.
You should be able to grab the expected out of a few test cases in url-loader
I think everything is either fixed or addressed now :) |
Just information, webpack-contrib/sass-loader#490 maybe related |
@michael-ciniawsky, @evilebottnawi: Have you seen my comments? I think I've either fixed or addressed everything now, could you have a look at it again? |
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
Why we change '
to "
? Can we save original quotes?
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.
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 escapeSingle(require(<resource>))
).
I skipped it, since I can't really imagine a scenario where it would be important to use single quotes. svg-url-loader
already assumes double-quotes, since this is what is commonly used in HTML, I assume other loaders will make similar choices. If not, the worst case scenario is a slightly longer string.
If this is implemented, there should maybe also be an escapeUnquoted
function for handling unquoted modules. That way the quotes used will be entirely up to the user, the loader will just ensure valid CSS is produced.
This Pull Request updates dependency [css-loader](https://github.com/webpack-contrib/css-loader) from `^0.28.11` to `^1.0.0` <details> <summary>Release Notes</summary> ### [`v1.0.0`](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md#​100httpsgithubcomwebpack-contribcss-loadercomparev02811v100-2018-07-06) [Compare Source](webpack-contrib/css-loader@v0.28.11...v1.0.0) ##### BREAKING CHANGES * remove `minimize` option, use [`postcss-loader`](https://github.com/postcss/postcss-loader) with [`cssnano`](https://github.com/cssnano/cssnano) or use [`optimize-cssnano-plugin`](https://github.com/intervolga/optimize-cssnano-plugin) plugin * remove `module` option, use `modules` option instead * remove `camelcase` option, use `camelCase` option instead * remove `root` option, use [`postcss-loader`](https://github.com/postcss/postcss-loader) with [`postcss-url`](https://github.com/postcss/postcss-url) plugin * remove `alias` option, use [`resolve.alias`](https://webpack.js.org/configuration/resolve/) feature or use [`postcss-loader`](https://github.com/postcss/postcss-loader) with [`postcss-url`](https://github.com/postcss/postcss-url) plugin * update `postcss` to `6` version * minimum require `nodejs` version is `6.9` * minimum require `webpack` version is `4` #### [0.28.11](webpack-contrib/css-loader@v0.28.10...v0.28.11) (2018-03-16) ##### Bug Fixes * **lib/processCss:** don't check `mode` for `url` handling (`options.modules`) ([#​698](`https://github.com/webpack-contrib/css-loader/issues/698`)) ([c788450](webpack-contrib/css-loader@c788450)) #### [0.28.10](webpack-contrib/css-loader@v0.28.9...v0.28.10) (2018-02-22) ##### Bug Fixes * **getLocalIdent:** add `rootContext` support (`webpack >= v4.0.0`) ([#​681](`https://github.com/webpack-contrib/css-loader/issues/681`)) ([9f876d2](webpack-contrib/css-loader@9f876d2)) #### [0.28.9](webpack-contrib/css-loader@v0.28.8...v0.28.9) (2018-01-17) ##### Bug Fixes * ignore invalid URLs (`url()`) ([#​663](`https://github.com/webpack-contrib/css-loader/issues/663`)) ([d1d8221](webpack-contrib/css-loader@d1d8221)) #### [0.28.8](webpack-contrib/css-loader@v0.28.7...v0.28.8) (2018-01-05) ##### Bug Fixes * **loader:** correctly check if source map is `undefined` ([#​641](`https://github.com/webpack-contrib/css-loader/issues/641`)) ([0dccfa9](webpack-contrib/css-loader@0dccfa9)) * proper URL escaping and wrapping (`url()`) ([#​627](`https://github.com/webpack-contrib/css-loader/issues/627`)) ([8897d44](webpack-contrib/css-loader@8897d44)) #### [0.28.7](webpack-contrib/css-loader@v0.28.6...v0.28.7) (2017-08-30) ##### Bug Fixes * pass resolver to `localsLoader` (`options.alias`) ([#​601](`https://github.com/webpack/css-loader/issues/601`)) ([8f1b57c](webpack-contrib/css-loader@8f1b57c)) #### [0.28.6](webpack-contrib/css-loader@v0.28.5...v0.28.6) (2017-08-30) ##### Bug Fixes * add support for aliases starting with `/` (`options.alias`) ([#​597](`https://github.com/webpack/css-loader/issues/597`)) ([63567f2](webpack-contrib/css-loader@63567f2)) #### [0.28.5](webpack-contrib/css-loader@v0.28.4...v0.28.5) (2017-08-17) ##### Bug Fixes * match mutliple dashes (`options.camelCase`) ([#​556](`https://github.com/webpack/css-loader/issues/556`)) ([1fee601](webpack-contrib/css-loader@1fee601)) * stricter `[@import]` tolerance ([#​593](`https://github.com/webpack/css-loader/issues/593`)) ([2e4ec09](webpack-contrib/css-loader@2e4ec09)) #### [0.28.4](webpack-contrib/css-loader@v0.28.3...v0.28.4) (2017-05-30) ##### Bug Fixes * preserve leading underscore in class names ([#​543](`https://github.com/webpack/css-loader/issues/543`)) ([f6673c8](webpack-contrib/css-loader@f6673c8)) #### [0.28.3](webpack-contrib/css-loader@v0.28.2...v0.28.3) (2017-05-25) ##### Bug Fixes * correct plugin order for CSS Modules ([#​534](`https://github.com/webpack/css-loader/issues/534`)) ([b90f492](webpack-contrib/css-loader@b90f492)) #### [0.28.2](webpack-contrib/css-loader@v0.28.1...v0.28.2) (2017-05-22) ##### Bug Fixes * source maps path on `windows` ([#​532](`https://github.com/webpack/css-loader/issues/532`)) ([c3d0d91](webpack-contrib/css-loader@c3d0d91)) #### [0.28.1](webpack-contrib/css-loader@v0.28.0...v0.28.1) (2017-05-02) ##### Bug Fixes * allow to specify a full hostname as a root URL ([#​521](`https://github.com/webpack/css-loader/issues/521`)) ([06d27a1](webpack-contrib/css-loader@06d27a1)) * case insensitivity of [@​import] ([#​514](`https://github.com/webpack/css-loader/issues/514`)) ([de4356b](webpack-contrib/css-loader@de4356b)) * don't handle empty [@​import] and url() ([#​513](`https://github.com/webpack/css-loader/issues/513`)) ([868fc94](webpack-contrib/css-loader@868fc94)) * imported variables are replaced in exports if followed by a comma ([#​504](`https://github.com/webpack/css-loader/issues/504`)) ([956bad7](webpack-contrib/css-loader@956bad7)) * loader now correctly handles `url` with space(s) ([#​495](`https://github.com/webpack/css-loader/issues/495`)) ([534ea55](webpack-contrib/css-loader@534ea55)) * url with a trailing space is now handled correctly ([#​494](`https://github.com/webpack/css-loader/issues/494`)) ([e1ec4f2](webpack-contrib/css-loader@e1ec4f2)) * use `btoa` instead `Buffer` ([#​501](`https://github.com/webpack/css-loader/issues/501`)) ([fbb0714](webpack-contrib/css-loader@fbb0714)) ##### Performance Improvements * generate source maps only when explicitly set ([#​478](`https://github.com/webpack/css-loader/issues/478`)) ([b8f5c8f](webpack-contrib/css-loader@b8f5c8f)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com).
What kind of change does this PR introduce?
Bugfix
Did you add tests for your changes?
Yes. 2 existing tests changed to account for new behaviour (no single-quotes), 6 new tests of escaping.
If relevant, did you update the README?
I don't believe it's relevant. Unless just to document the quoting behaviour.
Summary
Fixes #615. Previously, modules had their quotes stripped unless the module-path contained a space. Now, quotes are always stripped and the URL returned from the module is escaped and wrapped in double-quotes if necessary. Modules returning a quote-wrapped string will have their own wrapping-quotes stripped before wrapping/escaping.
Does this PR introduce a breaking change?
I don't think it does, modules wrapping in quotes like svg-url-loader should still function.
The only consequence is that it is impossible to wrap in single-quotes, although it's hard to see when this would be a necessity.
Other information
There is one weird case I'm not sure how to handle: newlines should be escaped in the quoted url. The spec states that only line feeds (
\n
) count as newlines, since carriage return (\r
) and form feed (\f
) are automatically converted during preprocessing. However, if left unescaped they would be converted to unescaped line feeds. If escaped, they wouldn't be converted. I'm not sure if the best step would be to convert them to escaped line feeds manually or if it even matters. Thoughts?