astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
117 lines (116 loc) • 4.31 kB
JavaScript
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import astroIslandPrebuilt from "../../runtime/server/astro-island.prebuilt.js";
import astroIslandPrebuiltDev from "../../runtime/server/astro-island.prebuilt-dev.js";
import { ISLAND_STYLES } from "../../runtime/server/astro-island-styles.js";
import { generateCspDigest } from "../encryption.js";
export function shouldTrackCspHashes(csp) {
return csp === true || typeof csp === "object";
}
export function getAlgorithm(csp) {
if (csp === true) {
return "SHA-256";
}
return csp.algorithm;
}
export function getScriptResources(csp) {
if (csp === true) {
return [];
}
return csp.scriptDirective?.resources ?? [];
}
export function getScriptHashes(csp) {
if (csp === true) {
return [];
}
return csp.scriptDirective?.hashes ?? [];
}
export function getStyleResources(csp) {
if (csp === true) {
return [];
}
return csp.styleDirective?.resources ?? [];
}
export function getStyleHashes(csp) {
if (csp === true) {
return [];
}
return csp.styleDirective?.hashes ?? [];
}
// Unlike other helpers like getStyleResources, getDirectives has more logic
// because it has to collect and deduplicate font resources from both the user
// config and the vite plugin for fonts
export function getDirectives(settings) {
const { csp } = settings.config.security;
if (!shouldTrackCspHashes(csp)) {
return [];
}
const userDirectives = csp === true ? [] : [...csp.directives ?? []];
const fontResources = Array.from(settings.injectedCsp.fontResources.values());
if (fontResources.length === 0) {
// If no font resources, just return user directives
return userDirectives;
}
const fontSrcIndex = userDirectives.findIndex((e) => e.startsWith("font-src"));
if (fontSrcIndex === -1) {
// Add new font-src directive
return [...userDirectives, `font-src ${fontResources.join(" ")}`];
}
// Merge and deduplicate font-src resources
const existing = userDirectives[fontSrcIndex].split(/\s+/).slice(1).filter(Boolean);
const merged = Array.from(new Set([...existing, ...fontResources]));
userDirectives[fontSrcIndex] = `font-src ${merged.join(" ")}`;
return userDirectives;
}
export function getStrictDynamic(csp) {
if (csp === true) {
return false;
}
return csp.scriptDirective?.strictDynamic ?? false;
}
export async function trackStyleHashes({ internals, settings, algorithm, clientRoot }) {
const clientStyleHashes = [];
for (const [_, page] of internals.pagesByViteID.entries()) {
for (const style of page.styles) {
if (style.sheet.type === "inline") {
clientStyleHashes.push(await generateCspDigest(style.sheet.content, algorithm));
}
}
}
for (const clientAsset in internals.clientChunksAndAssets) {
const contents = readFileSync(fileURLToPath(new URL(clientAsset, clientRoot)), "utf-8");
if (clientAsset.endsWith(".css") || clientAsset.endsWith(".css")) {
clientStyleHashes.push(await generateCspDigest(contents, algorithm));
}
}
if (settings.renderers.length > 0) {
clientStyleHashes.push(await generateCspDigest(ISLAND_STYLES, algorithm));
}
return clientStyleHashes;
}
export async function trackScriptHashes({ internals, settings, algorithm, clientRoot }) {
const clientScriptHashes = [];
for (const script of internals.inlinedScripts.values()) {
clientScriptHashes.push(await generateCspDigest(script, algorithm));
}
for (const directiveContent of Array.from(settings.clientDirectives.values())) {
clientScriptHashes.push(await generateCspDigest(directiveContent, algorithm));
}
for (const clientAsset in internals.clientChunksAndAssets) {
const contents = readFileSync(fileURLToPath(new URL(clientAsset, clientRoot)), "utf-8");
if (clientAsset.endsWith(".js") || clientAsset.endsWith(".mjs")) {
clientScriptHashes.push(await generateCspDigest(contents, algorithm));
}
}
for (const script of settings.scripts) {
const { content, stage } = script;
if (stage === "head-inline" || stage === "before-hydration") {
clientScriptHashes.push(await generateCspDigest(content, algorithm));
}
}
if (settings.renderers.length > 0) {
clientScriptHashes.push(await generateCspDigest(astroIslandPrebuilt, algorithm));
clientScriptHashes.push(await generateCspDigest(astroIslandPrebuiltDev, algorithm));
}
return clientScriptHashes;
}