diff --git a/README.md b/README.md
index 45b62f9d..09115855 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,8 @@ The `STATS_FILE` parameter represents the output file produced by `webpack-bundl
- `TIMEOUT` is the number of seconds webpack_loader should wait for webpack to finish compiling before raising an exception. `0`, `None` or leaving the value out of settings disables timeouts
+- `INTEGRITY` is flag enabling [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) on rendered `'), result.rendered_content)
+ def test_integrity(self):
+ self.compile_bundles('webpack.config.integrity.js')
+
+ loader = get_loader(DEFAULT_CONFIG)
+ with patch.dict(loader.config, {'INTEGRITY': True}):
+ view = TemplateView.as_view(template_name='single.html')
+ request = self.factory.get('/')
+ result = view(request)
+
+ self.assertIn((
+ ''), result.rendered_content)
+ self.assertIn((
+ ''),
+ result.rendered_content
+ )
+
+ def test_integrity_missing_config(self):
+ self.compile_bundles('webpack.config.integrity.js')
+
+ loader = get_loader(DEFAULT_CONFIG)
+ # remove INTEGRITY from config completely to test backward compatibility
+ integrity_from_config = loader.config.pop('INTEGRITY')
+
+ view = TemplateView.as_view(template_name='single.html')
+ request = self.factory.get('/')
+ result = view(request)
+
+ self.assertIn((
+ ''), result.rendered_content
+ )
+ self.assertIn((
+ ''),
+ result.rendered_content
+ )
+
+ # return removed key
+ loader.config['INTEGRITY'] = integrity_from_config
+
+ def test_integrity_missing_hash(self):
+ self.compile_bundles('webpack.config.simple.js')
+
+ loader = get_loader(DEFAULT_CONFIG)
+ with patch.dict(loader.config, {'INTEGRITY': True}), self.assertRaises(WebpackLoaderBadStatsError):
+ view = TemplateView.as_view(template_name='single.html')
+ request = self.factory.get('/')
+ str(view(request).rendered_content)
+
def test_append_extensions(self):
self.compile_bundles('webpack.config.gzipTest.js')
view = TemplateView.as_view(template_name='append_extensions.html')
diff --git a/tests/webpack.config.integrity.js b/tests/webpack.config.integrity.js
new file mode 100644
index 00000000..d82de67b
--- /dev/null
+++ b/tests/webpack.config.integrity.js
@@ -0,0 +1,41 @@
+var path = require("path");
+var webpack = require('webpack');
+var BundleTracker = require('webpack-bundle-tracker');
+var MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+
+module.exports = {
+ context: __dirname,
+ entry: './assets/js/index',
+ output: {
+ path: path.resolve('./assets/django_webpack_loader_bundles/'),
+ filename: "[name].js"
+ },
+
+ plugins: [
+ new MiniCssExtractPlugin(),
+ new BundleTracker({path: __dirname, filename: './webpack-stats.json', integrity: true}),
+ ],
+
+ module: {
+ rules: [
+ // we pass the output from babel loader to react-hot loader
+ {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-react']
+ }
+ }
+ },
+ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }
+ ],
+ },
+
+ resolve: {
+ modules: ['node_modules'],
+ extensions: ['.js', '.jsx']
+ },
+}
diff --git a/webpack_loader/config.py b/webpack_loader/config.py
index c978d77f..8761b2f5 100644
--- a/webpack_loader/config.py
+++ b/webpack_loader/config.py
@@ -15,6 +15,7 @@
'TIMEOUT': None,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
+ 'INTEGRITY': False,
}
}
diff --git a/webpack_loader/loader.py b/webpack_loader/loader.py
index c15d3122..6d46384b 100644
--- a/webpack_loader/loader.py
+++ b/webpack_loader/loader.py
@@ -38,6 +38,19 @@ def get_assets(self):
return self._assets[self.name]
return self.load_assets()
+ def get_integrity_attr(self, chunk):
+ if not self.config.get('INTEGRITY'):
+ return ' '
+
+ integrity = chunk.get('integrity')
+ if not integrity:
+ raise WebpackLoaderBadStatsError(
+ "The stats file does not contain valid data: INTEGRITY is set to True, "
+ "but chunk does not contain \"integrity\" key. Maybe you forgot to add "
+ "integrity: true in your BundleTracker configuration?")
+
+ return ' integrity="{}" '.format(integrity.partition(' ')[0])
+
def filter_chunks(self, chunks):
filtered_chunks = []
@@ -53,9 +66,15 @@ def map_chunk_files_to_url(self, chunks):
assets = self.get_assets()
files = assets['assets']
+ add_integrity = self.config.get('INTEGRITY')
+
for chunk in chunks:
url = self.get_chunk_url(files[chunk])
- yield { 'name': chunk, 'url': url }
+
+ if add_integrity:
+ yield {'name': chunk, 'url': url, 'integrity': files[chunk].get('integrity')}
+ else:
+ yield {'name': chunk, 'url': url}
def get_chunk_url(self, chunk_file):
public_path = chunk_file.get('publicPath')
diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py
index 008fef44..b432dc49 100644
--- a/webpack_loader/utils.py
+++ b/webpack_loader/utils.py
@@ -35,8 +35,8 @@ def _filter_by_extension(bundle, extension):
yield chunk
-def _get_bundle(bundle_name, extension, config):
- bundle = get_loader(config).get_bundle(bundle_name)
+def _get_bundle(loader, bundle_name, extension):
+ bundle = loader.get_bundle(bundle_name)
if extension:
bundle = _filter_by_extension(bundle, extension)
return bundle
@@ -44,7 +44,8 @@ def _get_bundle(bundle_name, extension, config):
def get_files(bundle_name, extension=None, config='DEFAULT'):
'''Returns list of chunks from named bundle'''
- return list(_get_bundle(bundle_name, extension, config))
+ loader = get_loader(config)
+ return list(_get_bundle(loader, bundle_name, extension))
def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False):
@@ -58,8 +59,10 @@ def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs=
:return: a list of formatted tags as strings
'''
- bundle = _get_bundle(bundle_name, extension, config)
+ loader = get_loader(config)
+ bundle = _get_bundle(loader, bundle_name, extension)
tags = []
+
for chunk in bundle:
if chunk['name'].endswith(('.js', '.js.gz')):
if is_preload:
@@ -68,12 +71,21 @@ def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs=
).format(''.join([chunk['url'], suffix]), attrs))
else:
tags.append((
- ''
- ).format(''.join([chunk['url'], suffix]), attrs))
+ ''
+ ).format(
+ ''.join([chunk['url'], suffix]),
+ attrs,
+ loader.get_integrity_attr(chunk),
+ ))
elif chunk['name'].endswith(('.css', '.css.gz')):
tags.append((
- ''
- ).format(''.join([chunk['url'], suffix]), attrs, '"stylesheet"' if not is_preload else '"preload" as="style"'))
+ ''
+ ).format(
+ ''.join([chunk['url'], suffix]),
+ attrs,
+ '"stylesheet"' if not is_preload else '"preload" as="style"',
+ loader.get_integrity_attr(chunk),
+ ))
return tags