next-csrf
Version:
CSRF mitigation library for Next.js
14 lines • 4.8 kB
JavaScript
;exports.__esModule=true;exports.default=void 0;var _nodeHtmlParser=require("node-html-parser");var _constants=require("./constants");const MIDDLEWARE_TIME_BUDGET=10;const MAXIMUM_IMAGE_PRELOADS=2;const IMAGE_PRELOAD_SIZE_THRESHOLD=2500;const middlewareRegistry=[];function registerPostProcessor(name,middleware,condition){middlewareRegistry.push({name,middleware,condition:condition||null});}async function processHTML(html,data,options){// Don't parse unless there's at least one processor middleware
if(!middlewareRegistry[0]){return html;}const postProcessData={preloads:{images:[]}};const root=(0,_nodeHtmlParser.parse)(html);let document=html;// Calls the middleware, with some instrumentation and logging
async function callMiddleWare(middleware,name){let timer=Date.now();middleware.inspect(root,postProcessData,data);const inspectTime=Date.now()-timer;document=await middleware.mutate(document,postProcessData,data);timer=Date.now()-timer;if(timer>MIDDLEWARE_TIME_BUDGET){console.warn(`The postprocess middleware "${name}" took ${timer}ms(${inspectTime}, ${timer-inspectTime}) to complete. This is longer than the ${MIDDLEWARE_TIME_BUDGET} limit.`);}return;}for(let i=0;i<middlewareRegistry.length;i++){let middleware=middlewareRegistry[i];if(!middleware.condition||middleware.condition(options)){await callMiddleWare(middlewareRegistry[i].middleware,middlewareRegistry[i].name);}}return document;}class FontOptimizerMiddleware{constructor(){this.fontDefinitions=[];this.mutate=async(markup,_data,options)=>{let result=markup;if(!options.getFontDefinition){return markup;}for(const key in this.fontDefinitions){const url=this.fontDefinitions[key];if(result.indexOf(`<style data-href="${url}">`)>-1){// The font is already optimized and probably the response is cached
continue;}const fontContent=options.getFontDefinition(url);result=result.replace('</head>',`<style data-href="${url}">${fontContent}</style></head>`);}return result;};}inspect(originalDom,_data,options){if(!options.getFontDefinition){return;}// collecting all the requested font definitions
originalDom.querySelectorAll('link').filter(tag=>tag.getAttribute('rel')==='stylesheet'&&tag.hasAttribute('data-href')&&_constants.OPTIMIZED_FONT_PROVIDERS.some(url=>{const dataHref=tag.getAttribute('data-href');return dataHref?dataHref.startsWith(url):false;})).forEach(element=>{const url=element.getAttribute('data-href');if(url){this.fontDefinitions.push(url);}});}}class ImageOptimizerMiddleware{constructor(){this.mutate=async(markup,_data)=>{let result=markup;let imagePreloadTags=_data.preloads.images.filter(imgHref=>!preloadTagAlreadyExists(markup,imgHref)).reduce((acc,imgHref)=>acc+`<link rel="preload" href="${imgHref}" as="image"/>`,'');return result.replace(/<link rel="preload"/,`${imagePreloadTags}<link rel="preload"`);};}inspect(originalDom,_data){const imgElements=originalDom.querySelectorAll('img');let eligibleImages=[];for(let i=0;i<imgElements.length;i++){if(isImgEligible(imgElements[i])){eligibleImages.push(imgElements[i]);}if(eligibleImages.length>=MAXIMUM_IMAGE_PRELOADS){break;}}_data.preloads.images=[];for(const imgEl of eligibleImages){const src=imgEl.getAttribute('src');if(src){_data.preloads.images.push(src);}}}}function isImgEligible(imgElement){return imgElement.hasAttribute('src')&&imageIsNotTooSmall(imgElement)&&imageIsNotHidden(imgElement);}function preloadTagAlreadyExists(html,href){const regex=new RegExp(`<link[^>]*href[^>]*${href}`);return html.match(regex);}function imageIsNotTooSmall(imgElement){// Skip images without both height and width--we don't know enough to say if
// they are too small
if(!(imgElement.hasAttribute('height')&&imgElement.hasAttribute('width'))){return true;}try{const heightAttr=imgElement.getAttribute('height');const widthAttr=imgElement.getAttribute('width');if(!heightAttr||!widthAttr){return true;}if(parseInt(heightAttr)*parseInt(widthAttr)<=IMAGE_PRELOAD_SIZE_THRESHOLD){return false;}}catch(err){return true;}return true;}// Traverse up the dom from each image to see if it or any of it's
// ancestors have the hidden attribute.
function imageIsNotHidden(imgElement){let activeElement=imgElement;while(activeElement.parentNode){if(activeElement.hasAttribute('hidden')){return false;}activeElement=activeElement.parentNode;}return true;}// Initialization
registerPostProcessor('Inline-Fonts',new FontOptimizerMiddleware(),// Using process.env because passing Experimental flag through loader is not possible.
// @ts-ignore
options=>options.optimizeFonts||process.env.__NEXT_OPTIMIZE_FONTS);registerPostProcessor('Preload Images',new ImageOptimizerMiddleware(),// @ts-ignore
options=>options.optimizeImages||process.env.__NEXT_OPTIMIZE_IMAGES);var _default=processHTML;exports.default=_default;
//# sourceMappingURL=post-process.js.map