csp.js 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. 'use strict';
  2. const extend = require('extend');
  3. const platform = require('platform');
  4. const utils = require('../utils');
  5. const HEADER = [
  6. 'x-content-security-policy',
  7. 'content-security-policy',
  8. ];
  9. const REPORT_ONLY_HEADER = [
  10. 'x-content-security-policy-report-only',
  11. 'content-security-policy-report-only',
  12. ];
  13. module.exports = options => {
  14. return async function csp(ctx, next) {
  15. await next();
  16. const opts = utils.merge(options, ctx.securityOptions.csp);
  17. if (utils.checkIfIgnore(opts, ctx)) return;
  18. let finalHeader;
  19. let value;
  20. const matchedOption = extend(true, {}, opts.policy);
  21. const isIE = platform.parse(ctx.header['user-agent']).name === 'IE';
  22. const bufArray = [];
  23. const headers = opts.reportOnly ? REPORT_ONLY_HEADER : HEADER;
  24. if (isIE && opts.supportIE) {
  25. finalHeader = headers[0];
  26. } else {
  27. finalHeader = headers[1];
  28. }
  29. for (const key in matchedOption) {
  30. value = matchedOption[key];
  31. value = Array.isArray(value) ? value : [ value ];
  32. // Other arrays are splitted into strings EXCEPT `sandbox`
  33. if (key === 'sandbox' && value[0] === true) {
  34. bufArray.push(key);
  35. } else {
  36. if (key === 'script-src') {
  37. const hasNonce = value.some(function(val) {
  38. return val.indexOf('nonce-') !== -1;
  39. });
  40. if (!hasNonce) {
  41. value.push('\'nonce-' + ctx.nonce + '\'');
  42. }
  43. }
  44. value = value.map(function(d) {
  45. if (d.startsWith('.')) {
  46. d = '*' + d;
  47. }
  48. return d;
  49. });
  50. bufArray.push(key + ' ' + value.join(' '));
  51. }
  52. }
  53. const headerString = bufArray.join(';');
  54. ctx.set(finalHeader, headerString);
  55. ctx.set('x-csp-nonce', ctx.nonce);
  56. };
  57. };