/**
* Template Handler Middleware
* Handles injecting CSP nonces into HTML templates
*/
const fs = require('fs');
const path = require('path');
const winston = require('winston');
const { generateRichSnippet, escapeHtmlAttr } = require('./rich_snippet');
// Cache for HTML templates
const templateCache = {};
function injectRichSnippetIntoHtml(html, snippet) {
if (!snippet) return html;
const metaTags = [
``,
``,
``,
``,
``,
``,
``,
``,
``,
``
].join('\n\t\t');
// Replace the document title if present.
html = html.replace(/
[^<]*<\/title>/i, `${escapeHtmlAttr(snippet.title)}`);
// Inject tags before .
if (/<\/head>/i.test(html)) {
return html.replace(/<\/head>/i, `\t\t${metaTags}\n\n\t`);
}
return html + '\n' + metaTags;
}
/**
* Template handler middleware
* Preprocesses HTML templates and injects the CSP nonce
*/
function templateHandlerMiddleware(staticDir, options) {
const opts = options || {};
const store = opts.store;
const staticDocuments = opts.documents || {};
const config = opts.config || {};
// Load the index.html template at startup to avoid reading from disk on each request
try {
const indexPath = path.join(staticDir, 'index.html');
templateCache[indexPath] = fs.readFileSync(indexPath, 'utf8');
winston.debug('Loaded template: ' + indexPath);
} catch (err) {
winston.error('Failed to load index.html template at startup:', err);
}
return function(req, res, next) {
// Only process specific URLs
if (req.url === '/' || req.url.match(/^\/[a-zA-Z0-9_-]+$/)) {
const indexPath = path.join(staticDir, 'index.html');
const isPasteRoute = req.url !== '/';
// If template is not in cache, try to load it
if (!templateCache[indexPath]) {
try {
templateCache[indexPath] = fs.readFileSync(indexPath, 'utf8');
winston.debug('Loaded template on demand: ' + indexPath);
} catch (err) {
winston.error('Error reading index.html template:', err);
return next();
}
}
// Get the CSP nonce from the request (set by CSP middleware)
const nonce = req.cspNonce || '';
const renderAndSend = function(pasteContent) {
// If the response was already sent (or the client disconnected), do nothing.
if (res.headersSent || res.writableEnded) {
return;
}
// Process the template - replace all nonce placeholders
let html;
try {
// Replace {{cspNonce}} placeholders
html = templateCache[indexPath].replace(/\{\{cspNonce\}\}/g, nonce);
// If this is a paste route and we have content, inject rich snippet tags.
if (isPasteRoute && pasteContent) {
const key = req.url.slice(1);
const snippet = generateRichSnippet({ key, content: pasteContent, req, config });
html = injectRichSnippetIntoHtml(html, snippet);
}
// Also ensure any