"use strict" Object.defineProperties(exports, {__esModule: {value: true}, [Symbol.toStringTag]: {value: "Module"}}) const dangerouslyDisableDefaultSrc = Symbol("dangerouslyDisableDefaultSrc") const DEFAULT_DIRECTIVES = { "default-src": ["'self'"], "base-uri": ["'self'"], "font-src": ["'self'", "https:", "data:"], "form-action": ["'self'"], "frame-ancestors": ["'self'"], "img-src": ["'self'", "data:"], "object-src": ["'none'"], "script-src": ["'self'"], "script-src-attr": ["'none'"], "style-src": ["'self'", "https:", "'unsafe-inline'"], "upgrade-insecure-requests": [] } const SHOULD_BE_QUOTED = new Set(["none", "self", "strict-dynamic", "report-sample", "inline-speculation-rules", "unsafe-inline", "unsafe-eval", "unsafe-hashes", "wasm-unsafe-eval"]) const getDefaultDirectives = () => Object.assign({}, DEFAULT_DIRECTIVES) const dashify = str => str.replace(/[A-Z]/g, capitalLetter => "-" + capitalLetter.toLowerCase()) const isDirectiveValueInvalid = directiveValue => /;|,/.test(directiveValue) const shouldDirectiveValueEntryBeQuoted = directiveValueEntry => SHOULD_BE_QUOTED.has(directiveValueEntry) || directiveValueEntry.startsWith("nonce-") || directiveValueEntry.startsWith("sha256-") || directiveValueEntry.startsWith("sha384-") || directiveValueEntry.startsWith("sha512-") const warnIfDirectiveValueEntryShouldBeQuoted = value => { if (shouldDirectiveValueEntryBeQuoted(value)) { console.warn(`Content-Security-Policy got directive value \`${value}\` which should be single-quoted and changed to \`'${value}'\`. This will be an error in future versions of Helmet.`) } } const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) function normalizeDirectives(options) { const defaultDirectives = getDefaultDirectives() const {useDefaults = true, directives: rawDirectives = defaultDirectives} = options const result = new Map() const directiveNamesSeen = new Set() const directivesExplicitlyDisabled = new Set() for (const rawDirectiveName in rawDirectives) { if (!has(rawDirectives, rawDirectiveName)) { continue } if (rawDirectiveName.length === 0 || /[^a-zA-Z0-9-]/.test(rawDirectiveName)) { throw new Error(`Content-Security-Policy received an invalid directive name ${JSON.stringify(rawDirectiveName)}`) } const directiveName = dashify(rawDirectiveName) if (directiveNamesSeen.has(directiveName)) { throw new Error(`Content-Security-Policy received a duplicate directive ${JSON.stringify(directiveName)}`) } directiveNamesSeen.add(directiveName) const rawDirectiveValue = rawDirectives[rawDirectiveName] let directiveValue if (rawDirectiveValue === null) { if (directiveName === "default-src") { 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`.") } directivesExplicitlyDisabled.add(directiveName) continue } else if (typeof rawDirectiveValue === "string") { directiveValue = [rawDirectiveValue] } else if (!rawDirectiveValue) { throw new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`) } else if (rawDirectiveValue === dangerouslyDisableDefaultSrc) { if (directiveName === "default-src") { directivesExplicitlyDisabled.add("default-src") continue } else { throw new Error(`Content-Security-Policy: tried to disable ${JSON.stringify(directiveName)} as if it were default-src; simply omit the key`) } } else { directiveValue = rawDirectiveValue } for (const element of directiveValue) { if (typeof element === "string") { if (isDirectiveValueInvalid(element)) { throw new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`) } warnIfDirectiveValueEntryShouldBeQuoted(element) } } result.set(directiveName, directiveValue) } if (useDefaults) { Object.entries(defaultDirectives).forEach(([defaultDirectiveName, defaultDirectiveValue]) => { if (!result.has(defaultDirectiveName) && !directivesExplicitlyDisabled.has(defaultDirectiveName)) { result.set(defaultDirectiveName, defaultDirectiveValue) } }) } if (!result.size) { throw new Error("Content-Security-Policy has no directives. Either set some or disable the header") } if (!result.has("default-src") && !directivesExplicitlyDisabled.has("default-src")) { 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`.") } return result } function getHeaderValue(req, res, normalizedDirectives) { let err const result = [] normalizedDirectives.forEach((rawDirectiveValue, directiveName) => { let directiveValue = "" for (const element of rawDirectiveValue) { if (typeof element === "function") { const newElement = element(req, res) warnIfDirectiveValueEntryShouldBeQuoted(newElement) directiveValue += " " + newElement } else { directiveValue += " " + element } } if (!directiveValue) { result.push(directiveName) } else if (isDirectiveValueInvalid(directiveValue)) { err = new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`) } else { result.push(`${directiveName}${directiveValue}`) } }) return err ? err : result.join(";") } const contentSecurityPolicy = function contentSecurityPolicy(options = {}) { const headerName = options.reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy" const normalizedDirectives = normalizeDirectives(options) return function contentSecurityPolicyMiddleware(req, res, next) { const result = getHeaderValue(req, res, normalizedDirectives) if (result instanceof Error) { next(result) } else { res.setHeader(headerName, result) next() } } } contentSecurityPolicy.getDefaultDirectives = getDefaultDirectives contentSecurityPolicy.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc exports.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc exports.default = contentSecurityPolicy exports.getDefaultDirectives = getDefaultDirectives module.exports = exports.default module.exports.default = module.exports