UNPKG

csp-header

Version:

Content-Security-Policy header generator

180 lines (147 loc) 3.97 kB
import { ALLOWED_DIRECTIVES } from "./constants/directives"; import { NONE } from "./constants/values"; import { CSPHeaderParams, CSPDirectives, CSPDirectiveName, CSPDirectiveValue, CSPPreset, CSPPresetsArray, CSPListDirectiveValue, } from "./types"; export * from "./types"; export * from "./constants/directives"; export * from "./constants/values"; /** * Build CSP header value from params */ export function getCSP(params: CSPHeaderParams = {}): string { const { directives = {}, presets = {}, reportUri } = params; const presetsList = normalizePresetsList(presets); const mergedPolicies = applyPresets(directives, presetsList); return policyToString(mergedPolicies, reportUri); } /** * Build CSP nonce string */ export function nonce(nonceKey: string): string { return `'nonce-${nonceKey}'`; } /** * Build CSP header value from resolved policy */ function policyToString( directives: Partial<CSPDirectives>, reportUri?: string ): string { const cspStringParts: string[] = []; for (const directiveName in directives) { if (!directives.hasOwnProperty(directiveName)) { continue; } const directiveValue = directives[directiveName as keyof CSPDirectives]; if (!directiveValue) { continue; } const directiveRulesString = getDirectiveString( directiveName as CSPDirectiveName, directiveValue ); if (directiveRulesString) { cspStringParts.push(directiveRulesString); } } if (reportUri) { cspStringParts.push(getReportUriDirective(reportUri)); } return cspStringParts.join(" "); } /** * Build directive rules part of CSP header value */ function getDirectiveString( directiveName: CSPDirectiveName, directiveValue: CSPDirectiveValue ): string { if (typeof directiveValue === "boolean") { return `${directiveName};`; } if (typeof directiveValue === "string") { return `${directiveName} ${directiveValue};`; } if (Array.isArray(directiveValue)) { const valueString = (directiveValue as CSPListDirectiveValue) .filter(Boolean) .join(" "); return `${directiveName} ${valueString};`; } return ""; } /** * Build report-uri directive */ function getReportUriDirective(reportUri: string): string { return `report-uri ${reportUri};`; } /** * Normalize different presets list formats to array format */ function normalizePresetsList(presets: CSPPreset): CSPPresetsArray { return Array.isArray(presets) ? presets : Object.values(presets); } /** * Merges presets to policy */ function applyPresets( directives: Partial<CSPDirectives>, presets: CSPPresetsArray ): Partial<CSPDirectives> { const mergedPolicies: Partial<CSPDirectives> = {}; for (const preset of [directives, ...presets]) { for (const directiveName in preset) { if (!(directiveName in ALLOWED_DIRECTIVES)) { continue; } const currentRules = mergedPolicies[directiveName as keyof CSPDirectives]; const presetRules = preset[directiveName as keyof CSPDirectives]; if (presetRules === undefined) { continue; } (mergedPolicies[ directiveName as keyof CSPDirectives ] as CSPDirectiveValue) = mergeDirectiveRules( currentRules, presetRules ); } } return mergedPolicies; } function mergeDirectiveRules( directiveValue1: CSPDirectiveValue = "", directiveValue2: CSPDirectiveValue = "" ): CSPDirectiveValue { if (directiveValue1 === undefined) { return directiveValue2; } if (directiveValue2 === undefined) { return directiveValue1; } if (Array.isArray(directiveValue1) && Array.isArray(directiveValue2)) { const uniqRules = getUniqRules([ ...directiveValue1, ...directiveValue2, ]); const noneIndex = uniqRules.indexOf(NONE); // Remove "'none'" if there are other rules if (noneIndex >= 0 && uniqRules.length > 1) { uniqRules.splice(noneIndex, 1); } return uniqRules; } return directiveValue2; } function getUniqRules(rules: CSPListDirectiveValue): CSPListDirectiveValue { return Array.from(new Set(rules)); }