Skip to content

Commit 2fd8bc3

Browse files
authored
Add webpack compilation object to processfn (#81)
Allowing the ability to manipulate the webpack build to add an asset file to the build via the `compilation` object. See readme for an example on how to generate a file that can be included by NGINX.
1 parent 96491f9 commit 2fd8bc3

File tree

4 files changed

+101
-9
lines changed

4 files changed

+101
-9
lines changed

README.md

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ All inline JS and CSS will be hashed and inserted into the policy.
1717

1818
Install the plugin with npm:
1919

20-
```
20+
```bash
2121
npm i --save-dev csp-html-webpack-plugin
2222
```
2323

2424
## Basic Usage
2525

2626
Include the following in your webpack config:
2727

28-
```
28+
```js
2929
const HtmlWebpackPlugin = require('html-webpack-plugin');
3030
const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin');
3131

@@ -90,6 +90,7 @@ This `CspHtmlWebpackPlugin` accepts 2 params with the following structure:
9090
- `builtPolicy`: a `string` containing the completed policy;
9191
- `htmlPluginData`: the `HtmlWebpackPlugin` `object`;
9292
- `$`: the `cheerio` object of the html file currently being processed
93+
- `compilation`: Internal webpack object to manipulate the build
9394

9495
### `HtmlWebpackPlugin`
9596

@@ -105,6 +106,7 @@ The plugin also adds a new config option onto each `HtmlWebpackPlugin` instance:
105106
- `builtPolicy`: a `string` containing the completed policy;
106107
- `htmlPluginData`: the `HtmlWebpackPlugin` `object`;
107108
- `$`: the `cheerio` object of the html file currently being processed
109+
- `compilation`: Internal webpack object to manipulate the build
108110

109111
### Order of Precedence:
110112

@@ -125,7 +127,7 @@ In the case where a config object is defined in multiple places, it will be merg
125127

126128
#### Default Policy:
127129

128-
```
130+
```js
129131
{
130132
'base-uri': "'self'",
131133
'object-src': "'none'",
@@ -136,7 +138,7 @@ In the case where a config object is defined in multiple places, it will be merg
136138

137139
#### Default Additional Options:
138140

139-
```
141+
```js
140142
{
141143
enabled: true
142144
hashingMethod: 'sha256',
@@ -154,7 +156,7 @@ In the case where a config object is defined in multiple places, it will be merg
154156

155157
#### Full Default Configuration:
156158

157-
```
159+
```js
158160
new HtmlWebpackPlugin({
159161
cspPlugin: {
160162
enabled: true,
@@ -195,7 +197,49 @@ new CspHtmlWebpackPlugin({
195197
processFn: defaultProcessFn // defined in the plugin itself
196198
})
197199
```
200+
## Advanced Usage
201+
### Generating a file containing the CSP directives
198202

203+
Some specific directives require the CSP to be sent to the client via a response header (e.g. `report-uri` and `report-to`)
204+
You can set your own `processFn` callback to make this happen.
205+
206+
#### nginx
207+
208+
In your webpack config:
209+
210+
```js
211+
const RawSource = require('webpack-sources').RawSource;
212+
213+
function generateNginxHeaderFile(
214+
builtPolicy,
215+
_htmlPluginData,
216+
_obj,
217+
compilation
218+
) {
219+
const header =
220+
'add_header Content-Security-Policy "' +
221+
builtPolicy +
222+
'; report-uri /csp-report/ ";';
223+
compilation.emitAsset('nginx-csp-header.conf', new RawSource(header));
224+
}
225+
226+
module.exports = {
227+
{...},
228+
plugins: [
229+
new CspHtmlWebpackPlugin(
230+
{...}, {
231+
processFn: generateNginxHeaderFile
232+
})
233+
]
234+
};
235+
```
236+
In your nginx config:
237+
```nginx
238+
location / {
239+
...
240+
include /path/to/webpack/output/nginx-csp-header.conf
241+
}
242+
```
199243
## Contribution
200244

201245
Contributions are most welcome! Please see the included contributing file for more information.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"jest": "^26.6.3",
4949
"memory-fs": "^0.5.0",
5050
"prettier": "^2.2.1",
51-
"webpack": "^5.10.1"
51+
"webpack": "^5.10.1",
52+
"webpack-sources": "^2.2.0"
5253
}
5354
}

plugin.jest.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const path = require('path');
22
const crypto = require('crypto');
33
const HtmlWebpackPlugin = require('html-webpack-plugin');
4+
const { RawSource } = require('webpack-sources');
45
const {
56
WEBPACK_OUTPUT_DIR,
67
createWebpackConfig,
@@ -885,6 +886,7 @@ describe('CspHtmlWebpackPlugin', () => {
885886
expect(processFn).toHaveBeenCalledWith(
886887
builtPolicy,
887888
expect.anything(),
889+
expect.anything(),
888890
expect.anything()
889891
);
890892

@@ -932,12 +934,57 @@ describe('CspHtmlWebpackPlugin', () => {
932934
expect(processFn).toHaveBeenCalledWith(
933935
index1BuiltPolicy,
934936
expect.anything(),
937+
expect.anything(),
935938
expect.anything()
936939
);
937940

938941
done();
939942
});
940943
});
944+
945+
it('Allows to generate a file containing the policy', (done) => {
946+
function generateCSPFile(
947+
builtPolicy,
948+
_htmlPluginData,
949+
_obj,
950+
compilation
951+
) {
952+
compilation.emitAsset('csp.conf', new RawSource(builtPolicy));
953+
}
954+
const index1BuiltPolicy = `base-uri 'self'; object-src 'none'; script-src 'unsafe-inline' 'self' 'unsafe-eval' 'sha256-ixjZMYNfWQWawUHioWOx2jBsTmfxucX7IlwsMt2jWvc=' 'nonce-mockedbase64string-1' 'nonce-mockedbase64string-2'; style-src 'unsafe-inline' 'self' 'unsafe-eval' 'sha256-MqG77yUiqBo4MMVZAl09WSafnQY4Uu3cSdZPKxaf9sQ=' 'nonce-mockedbase64string-3'`;
955+
956+
const config = createWebpackConfig([
957+
new HtmlWebpackPlugin({
958+
filename: path.join(WEBPACK_OUTPUT_DIR, 'index-1.html'),
959+
template: path.join(
960+
__dirname,
961+
'test-utils',
962+
'fixtures',
963+
'with-script-and-style.html'
964+
),
965+
}),
966+
new CspHtmlWebpackPlugin(
967+
{},
968+
{
969+
processFn: generateCSPFile,
970+
}
971+
),
972+
]);
973+
974+
webpackCompile(config, (csps, selectors, fileSystem) => {
975+
const cspFileContent = fileSystem
976+
.readFileSync(path.join(WEBPACK_OUTPUT_DIR, 'csp.conf'), 'utf8')
977+
.toString();
978+
979+
// it won't exist in the html file since we overwrote processFn
980+
expect(csps['index-1.html']).toBeUndefined();
981+
982+
// A file has been generated
983+
expect(cspFileContent).toEqual(index1BuiltPolicy);
984+
985+
done();
986+
});
987+
});
941988
});
942989

943990
describe('HTML parsing', () => {

plugin.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class CspHtmlWebpackPlugin {
311311
* @param htmlPluginData
312312
* @param compileCb
313313
*/
314-
processCsp(htmlPluginData, compileCb) {
314+
processCsp(compilation, htmlPluginData, compileCb) {
315315
const $ = cheerio.load(htmlPluginData.html, {
316316
decodeEntities: false,
317317
_useHtmlParser2: true,
@@ -343,7 +343,7 @@ class CspHtmlWebpackPlugin {
343343
),
344344
});
345345

346-
this.processFn(builtPolicy, htmlPluginData, $);
346+
this.processFn(builtPolicy, htmlPluginData, $, compilation);
347347

348348
return compileCb(null, htmlPluginData);
349349
}
@@ -360,7 +360,7 @@ class CspHtmlWebpackPlugin {
360360
);
361361
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
362362
'CspHtmlWebpackPlugin',
363-
this.processCsp.bind(this)
363+
this.processCsp.bind(this, compilation)
364364
);
365365
});
366366
}

0 commit comments

Comments
 (0)