'use strict'; const {GateError}=require('./http-request-gate.cjs'); const allowed=new Set(['content-disposition','content-type','content-language','cache-control','expires','last-modified','etag','location','set-cookie','vary','content-security-policy','content-security-policy-report-only','referrer-policy','permissions-policy','x-content-type-options','x-frame-options','strict-transport-security']); const invalid=value=>typeof value!=='string'||value.length>8192||[...value].some(character=>character.charCodeAt(0)<32&&character!=='\t'||character.charCodeAt(0)===127); /** Provisional buffered response. No streaming/compression forwarding. * Content-Length is recomputed from a private body copy. Hop-by-hop and * unrecognized headers are never forwarded. Set-Cookie may be a bounded * string array; values are forwarded separately without comma combining. */ function validateResponse(record,maxBytes=1048576) { if(!record||!Number.isSafeInteger(record.statusCode)||record.statusCode<200||record.statusCode>599||!Number.isSafeInteger(maxBytes)||maxBytes<0||!Buffer.isBuffer(record.body)||record.body.length>maxBytes||!record.headers||typeof record.headers!=='object'||Array.isArray(record.headers))throw new GateError('invalid-upstream-response'); const headers=Object.create(null); const seen=new Set(),hop=new Set(); for(const [key,value] of Object.entries(record.headers))if(key.toLowerCase()==='connection') { if(invalid(value))throw new GateError('invalid-upstream-response'); for(const token of value.split(',')){if(!/^[a-zA-Z0-9-]+$/.test(token.trim()))throw new GateError('invalid-upstream-response');hop.add(token.trim().toLowerCase());} } let size=0; for(const [key,value] of Object.entries(record.headers)) { if(!/^[a-zA-Z0-9-]+$/.test(key))throw new GateError('invalid-upstream-response'); const name=key.toLowerCase(); if(seen.has(name))throw new GateError('invalid-upstream-response');seen.add(name); if(name==='content-encoding'&&value!==undefined&&value!=='identity')throw new GateError('unsupported-upstream-encoding'); if(!allowed.has(name)||hop.has(name))continue; const values=Array.isArray(value)?value:[value]; if(values.length>32||(Array.isArray(value)&&name!=='set-cookie')||values.some(invalid))throw new GateError('invalid-upstream-response'); size+=key.length+values.reduce((sum,item)=>sum+item.length,0); if(size>32768)throw new GateError('invalid-upstream-response'); headers[name]=Array.isArray(value)?Object.freeze([...value]):value; } if((record.statusCode===204||record.statusCode===304)&&record.body.length)throw new GateError('invalid-upstream-response'); const body=Buffer.from(record.body); headers['content-length']=String(body.length);headers.connection='close'; return Object.freeze({statusCode:record.statusCode,headers:Object.freeze(headers),body}); } module.exports={validateResponse};