1 | "use strict"
|
2 | Object.defineProperty(exports, "__esModule", { value: true })
|
3 | exports.dangerouslyDisableDefaultSrc = exports.getDefaultDirectives = void 0
|
4 | const dangerouslyDisableDefaultSrc = Symbol("dangerouslyDisableDefaultSrc")
|
5 | exports.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc
|
6 | const DEFAULT_DIRECTIVES = {
|
7 | "default-src": ["'self'"],
|
8 | "base-uri": ["'self'"],
|
9 | "block-all-mixed-content": [],
|
10 | "font-src": ["'self'", "https:", "data:"],
|
11 | "frame-ancestors": ["'self'"],
|
12 | "img-src": ["'self'", "data:"],
|
13 | "object-src": ["'none'"],
|
14 | "script-src": ["'self'"],
|
15 | "script-src-attr": ["'none'"],
|
16 | "style-src": ["'self'", "https:", "'unsafe-inline'"],
|
17 | "upgrade-insecure-requests": []
|
18 | }
|
19 | const getDefaultDirectives = () => Object.assign({}, DEFAULT_DIRECTIVES)
|
20 | exports.getDefaultDirectives = getDefaultDirectives
|
21 | const dashify = str => str.replace(/[A-Z]/g, capitalLetter => "-" + capitalLetter.toLowerCase())
|
22 | const isDirectiveValueInvalid = directiveValue => /;|,/.test(directiveValue)
|
23 | const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
|
24 | function normalizeDirectives(options) {
|
25 | const defaultDirectives = getDefaultDirectives()
|
26 | const { useDefaults = false, directives: rawDirectives = defaultDirectives } = options
|
27 | const result = new Map()
|
28 | const directiveNamesSeen = new Set()
|
29 | const directivesExplicitlyDisabled = new Set()
|
30 | for (const rawDirectiveName in rawDirectives) {
|
31 | if (!has(rawDirectives, rawDirectiveName)) {
|
32 | continue
|
33 | }
|
34 | if (rawDirectiveName.length === 0 || /[^a-zA-Z0-9-]/.test(rawDirectiveName)) {
|
35 | throw new Error(`Content-Security-Policy received an invalid directive name ${JSON.stringify(rawDirectiveName)}`)
|
36 | }
|
37 | const directiveName = dashify(rawDirectiveName)
|
38 | if (directiveNamesSeen.has(directiveName)) {
|
39 | throw new Error(`Content-Security-Policy received a duplicate directive ${JSON.stringify(directiveName)}`)
|
40 | }
|
41 | directiveNamesSeen.add(directiveName)
|
42 | const rawDirectiveValue = rawDirectives[rawDirectiveName]
|
43 | let directiveValue
|
44 | if (rawDirectiveValue === null) {
|
45 | if (directiveName === "default-src") {
|
46 | throw new Error("Content-Security-Policy needs a default-src but it was set to `null`. If you really want to disable it, set it to `contentSecurityPolicy.dangerouslyDisableDefaultSrc`.")
|
47 | }
|
48 | directivesExplicitlyDisabled.add(directiveName)
|
49 | continue
|
50 | } else if (typeof rawDirectiveValue === "string") {
|
51 | directiveValue = [rawDirectiveValue]
|
52 | } else if (!rawDirectiveValue) {
|
53 | throw new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`)
|
54 | } else if (rawDirectiveValue === dangerouslyDisableDefaultSrc) {
|
55 | if (directiveName === "default-src") {
|
56 | directivesExplicitlyDisabled.add("default-src")
|
57 | continue
|
58 | } else {
|
59 | throw new Error(`Content-Security-Policy: tried to disable ${JSON.stringify(directiveName)} as if it were default-src; simply omit the key`)
|
60 | }
|
61 | } else {
|
62 | directiveValue = rawDirectiveValue
|
63 | }
|
64 | for (const element of directiveValue) {
|
65 | if (typeof element === "string" && isDirectiveValueInvalid(element)) {
|
66 | throw new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`)
|
67 | }
|
68 | }
|
69 | result.set(directiveName, directiveValue)
|
70 | }
|
71 | if (useDefaults) {
|
72 | Object.entries(defaultDirectives).forEach(([defaultDirectiveName, defaultDirectiveValue]) => {
|
73 | if (!result.has(defaultDirectiveName) && !directivesExplicitlyDisabled.has(defaultDirectiveName)) {
|
74 | result.set(defaultDirectiveName, defaultDirectiveValue)
|
75 | }
|
76 | })
|
77 | }
|
78 | if (!result.size) {
|
79 | throw new Error("Content-Security-Policy has no directives. Either set some or disable the header")
|
80 | }
|
81 | if (!result.has("default-src") && !directivesExplicitlyDisabled.has("default-src")) {
|
82 | throw new Error("Content-Security-Policy needs a default-src but none was provided. If you really want to disable it, set it to `contentSecurityPolicy.dangerouslyDisableDefaultSrc`.")
|
83 | }
|
84 | return result
|
85 | }
|
86 | function getHeaderValue(req, res, normalizedDirectives) {
|
87 | let err
|
88 | const result = []
|
89 | normalizedDirectives.forEach((rawDirectiveValue, directiveName) => {
|
90 | let directiveValue = ""
|
91 | for (const element of rawDirectiveValue) {
|
92 | directiveValue += " " + (element instanceof Function ? element(req, res) : element)
|
93 | }
|
94 | if (!directiveValue) {
|
95 | result.push(directiveName)
|
96 | } else if (isDirectiveValueInvalid(directiveValue)) {
|
97 | err = new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`)
|
98 | } else {
|
99 | result.push(`${directiveName}${directiveValue}`)
|
100 | }
|
101 | })
|
102 | return err ? err : result.join(";")
|
103 | }
|
104 | const contentSecurityPolicy = function contentSecurityPolicy(options = {}) {
|
105 | if ("loose" in options) {
|
106 | console.warn("Content-Security-Policy middleware no longer needs the `loose` parameter. You should remove it.")
|
107 | }
|
108 | if ("setAllHeaders" in options) {
|
109 | console.warn("Content-Security-Policy middleware no longer supports the `setAllHeaders` parameter. See <https://github.com/helmetjs/helmet/wiki/Setting-legacy-Content-Security-Policy-headers-in-Helmet-4>.")
|
110 | }
|
111 | ;["disableAndroid", "browserSniff"].forEach(deprecatedOption => {
|
112 | if (deprecatedOption in options) {
|
113 | console.warn(`Content-Security-Policy middleware no longer does browser sniffing, so you can remove the \`${deprecatedOption}\` option. See <https://github.com/helmetjs/csp/issues/97> for discussion.`)
|
114 | }
|
115 | })
|
116 | const headerName = options.reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy"
|
117 | const normalizedDirectives = normalizeDirectives(options)
|
118 | return function contentSecurityPolicyMiddleware(req, res, next) {
|
119 | const result = getHeaderValue(req, res, normalizedDirectives)
|
120 | if (result instanceof Error) {
|
121 | next(result)
|
122 | } else {
|
123 | res.setHeader(headerName, result)
|
124 | next()
|
125 | }
|
126 | }
|
127 | }
|
128 | contentSecurityPolicy.getDefaultDirectives = getDefaultDirectives
|
129 | contentSecurityPolicy.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc
|
130 | module.exports = contentSecurityPolicy
|
131 | exports.default = contentSecurityPolicy
|