UNPKG

6.06 kBJavaScriptView Raw
1"use strict"
2Object.defineProperty(exports, "__esModule", { value: true })
3exports.dangerouslyDisableDefaultSrc = exports.getDefaultDirectives = void 0
4const dangerouslyDisableDefaultSrc = Symbol("dangerouslyDisableDefaultSrc")
5exports.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc
6const 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}
19const getDefaultDirectives = () => Object.assign({}, DEFAULT_DIRECTIVES)
20exports.getDefaultDirectives = getDefaultDirectives
21const dashify = str => str.replace(/[A-Z]/g, capitalLetter => "-" + capitalLetter.toLowerCase())
22const isDirectiveValueInvalid = directiveValue => /;|,/.test(directiveValue)
23const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
24function 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}
86function 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}
104const 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}
128contentSecurityPolicy.getDefaultDirectives = getDefaultDirectives
129contentSecurityPolicy.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc
130module.exports = contentSecurityPolicy
131exports.default = contentSecurityPolicy