export const httpStreamOutput = function(readStream, name, http, downloadFlag, cacheControl) { readStream.on('data', data => { http.response.write(data); }); readStream.on('end', () => { // don't pass parameters to end() or it will be attached to the file's binary stream http.response.end(); }); readStream.on('error', (err) => { console.error(`Download stream error for file '${name}':`, err); http.response.statusCode = 404; http.response.end('not found'); }); if (cacheControl) { http.response.setHeader('Cache-Control', cacheControl); } // Set Content-Disposition header http.response.setHeader('Content-Disposition', getContentDisposition(name, http?.params?.query?.download)); // Add security headers to prevent XSS attacks const isSvgFile = name && name.toLowerCase().endsWith('.svg'); if (isSvgFile) { // For SVG files, add strict CSP to prevent script execution http.response.setHeader('Content-Security-Policy', "default-src 'none'; script-src 'none'; object-src 'none';"); http.response.setHeader('X-Content-Type-Options', 'nosniff'); http.response.setHeader('X-Frame-Options', 'DENY'); } }; /** will initiate download, if links are called with ?download="true" queryparam */ const getContentDisposition = (name, downloadFlag) => { // Force attachment disposition for SVG files to prevent XSS attacks const isSvgFile = name && name.toLowerCase().endsWith('.svg'); const forceAttachment = isSvgFile || downloadFlag === 'true'; const dispositionType = forceAttachment ? 'attachment;' : 'inline;'; const encodedName = encodeURIComponent(name); const dispositionName = `filename="${encodedName}"; filename=*UTF-8"${encodedName}";`; const dispositionEncoding = 'charset=utf-8'; return `${dispositionType} ${dispositionName} ${dispositionEncoding}`; };