fix(web): nonce JSON-LD scripts
This commit is contained in:
@@ -20,6 +20,7 @@ import type { RenderToPipeableStreamOptions } from "react-dom/server";
|
|||||||
import { renderToPipeableStream } from "react-dom/server";
|
import { renderToPipeableStream } from "react-dom/server";
|
||||||
|
|
||||||
import { DEFAULT_LOCALE, getLocaleFromPathname } from "~/i18n/config";
|
import { DEFAULT_LOCALE, getLocaleFromPathname } from "~/i18n/config";
|
||||||
|
import { nonceJsonLdScripts } from "~/lib/csp-stream.server";
|
||||||
import { getServerEnv } from "~/lib/env.server";
|
import { getServerEnv } from "~/lib/env.server";
|
||||||
|
|
||||||
export const streamTimeout = 5_000;
|
export const streamTimeout = 5_000;
|
||||||
@@ -113,7 +114,10 @@ export default function handleRequest(
|
|||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const stream = createReadableStreamFromReadable(body);
|
const responseBody = nonce
|
||||||
|
? body.pipe(nonceJsonLdScripts(nonce))
|
||||||
|
: body;
|
||||||
|
const stream = createReadableStreamFromReadable(responseBody);
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html");
|
||||||
responseHeaders.set("Content-Language", contentLanguage(request));
|
responseHeaders.set("Content-Language", contentLanguage(request));
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Transform } from "node:stream";
|
||||||
|
import type { TransformCallback } from "node:stream";
|
||||||
|
|
||||||
|
const JSON_LD_OPENING_TAG = Buffer.from('<script type="application/ld+json">');
|
||||||
|
|
||||||
|
export function nonceJsonLdScripts(nonce: string): Transform {
|
||||||
|
const nonceOpeningTag = Buffer.from(
|
||||||
|
`<script nonce="${nonce}" type="application/ld+json">`,
|
||||||
|
);
|
||||||
|
const retainedBytes = JSON_LD_OPENING_TAG.length - 1;
|
||||||
|
let pending = Buffer.alloc(0);
|
||||||
|
|
||||||
|
return new Transform({
|
||||||
|
transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback) {
|
||||||
|
const data = Buffer.concat([pending, chunk]);
|
||||||
|
const safeLimit = Math.max(0, data.length - retainedBytes);
|
||||||
|
let cursor = 0;
|
||||||
|
let match = data.indexOf(JSON_LD_OPENING_TAG, cursor);
|
||||||
|
|
||||||
|
while (match !== -1 && match < safeLimit) {
|
||||||
|
this.push(data.subarray(cursor, match));
|
||||||
|
this.push(nonceOpeningTag);
|
||||||
|
cursor = match + JSON_LD_OPENING_TAG.length;
|
||||||
|
match = data.indexOf(JSON_LD_OPENING_TAG, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeEnd = Math.max(cursor, safeLimit);
|
||||||
|
if (safeEnd > cursor) this.push(data.subarray(cursor, safeEnd));
|
||||||
|
pending = Buffer.from(data.subarray(safeEnd));
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
flush(callback: TransformCallback) {
|
||||||
|
let cursor = 0;
|
||||||
|
let match = pending.indexOf(JSON_LD_OPENING_TAG, cursor);
|
||||||
|
|
||||||
|
while (match !== -1) {
|
||||||
|
this.push(pending.subarray(cursor, match));
|
||||||
|
this.push(nonceOpeningTag);
|
||||||
|
cursor = match + JSON_LD_OPENING_TAG.length;
|
||||||
|
match = pending.indexOf(JSON_LD_OPENING_TAG, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.push(pending.subarray(cursor));
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user