Add packaged DLP broker and protected Windows service foundation
This commit is contained in:
1 parent
95d23f4e0b
commit
7b5057901a
140 files changed
+11215
-60
No files matched your search
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
class MultipartError extends Error{
|
||||
constructor(code){super(code);this.name='MultipartError';this.code=code;}
|
||||
}
|
||||
const fail=code=>{throw new MultipartError(code);};
|
||||
const token=/^[!#$%&'*+.^_`|~0-9a-z-]+$/i;
|
||||
function parameters(value){
|
||||
const main=/^([^;]+)(.*)$/.exec(value);if(!main)fail('invalid-multipart');
|
||||
const result=Object.create(null);let rest=main[2];
|
||||
while(rest){
|
||||
const key=/^;[ \t]*([!#$%&'*+.^_`|~0-9a-z-]+)[ \t]*=[ \t]*/i.exec(rest);
|
||||
if(!key)fail('invalid-multipart');rest=rest.slice(key[0].length);
|
||||
let parameter;
|
||||
if(rest.startsWith('"')){
|
||||
let index=1;parameter='';let closed=false;
|
||||
for(;index<rest.length;index++){
|
||||
const character=rest[index];
|
||||
if(character==='"'){closed=true;index++;break;}
|
||||
if(character==='\\'){
|
||||
index++;if(!['"','\\'].includes(rest[index]))fail('invalid-multipart');parameter+=rest[index];
|
||||
}else parameter+=character;
|
||||
}
|
||||
if(!closed)fail('invalid-multipart');rest=rest.slice(index);
|
||||
}else{
|
||||
const match=/^([^; \t]+)([ \t]*)(.*)$/.exec(rest);if(!match||!token.test(match[1]))fail('invalid-multipart');
|
||||
parameter=match[1];rest=match[3];
|
||||
}
|
||||
const name=key[1].toLowerCase();if(Object.hasOwn(result,name))fail('invalid-multipart');
|
||||
result[name]=parameter;rest=rest.replace(/^[ \t]+/,'');
|
||||
}
|
||||
return {main:main[1].trim().toLowerCase(),values:result};
|
||||
}
|
||||
function metadata(header){
|
||||
let text;try{text=new TextDecoder('utf-8',{fatal:true}).decode(header);}catch{fail('invalid-multipart');}
|
||||
const headers=Object.create(null);
|
||||
for(const line of text.split('\r\n')){
|
||||
const match=/^([^:]+):[ \t]*(.*)$/.exec(line);
|
||||
if(!match||!token.test(match[1])||[...match[2]].some(character=>{const code=character.charCodeAt(0);return code<32&&code!==9||code===127;}))fail('invalid-multipart');
|
||||
const name=match[1].toLowerCase();if(Object.hasOwn(headers,name))fail('invalid-multipart');headers[name]=match[2];
|
||||
}
|
||||
if(Object.keys(headers).some(name=>!['content-disposition','content-type'].includes(name)))fail('unsupported-multipart');
|
||||
if(!headers['content-disposition'])fail('invalid-multipart');
|
||||
const disposition=parameters(headers['content-disposition']),values=disposition.values;
|
||||
if(Object.keys(values).some(name=>name.endsWith('*')))fail('unsupported-multipart');
|
||||
if(disposition.main!=='form-data'||Object.keys(values).some(name=>!['name','filename'].includes(name))||typeof values.name!=='string'||!values.name||Buffer.byteLength(values.name)>256||[values.name,values.filename].some(value=>value!==undefined&&(Buffer.byteLength(value)>1024||[...value].some(character=>character.charCodeAt(0)<32||character.charCodeAt(0)===127))))fail('invalid-multipart');
|
||||
let contentType;
|
||||
if(headers['content-type']!==undefined){
|
||||
if(headers['content-type'].length>256)fail('multipart-limit');
|
||||
const type=parameters(headers['content-type']);
|
||||
if(!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(type.main))fail('invalid-multipart');
|
||||
if(type.main.startsWith('multipart/'))fail('unsupported-multipart');
|
||||
contentType=headers['content-type'];
|
||||
}
|
||||
return {name:values.name,...values.filename!==undefined?{filename:values.filename}:{},...contentType!==undefined?{contentType}:{}};
|
||||
}
|
||||
/** Full-buffer parser. No filesystem access or content logging. Returned part
|
||||
* buffers are independent private copies; dispose only after scanners finish.
|
||||
* Strict unsupported forms fail closed instead of claiming partial coverage.
|
||||
*/
|
||||
function parseMultipart(body,contentType,{maxBytes=16777216,maxParts=64,maxHeaderBytes=8192}={}){
|
||||
if(!Buffer.isBuffer(body)||typeof contentType!=='string'||![maxBytes,maxParts,maxHeaderBytes].every(Number.isSafeInteger)||maxBytes<0||maxBytes>16777216||maxParts<1||maxParts>256||maxHeaderBytes<1||maxHeaderBytes>32768)fail('invalid-multipart');
|
||||
if(body.length>maxBytes||contentType.length>256)fail('multipart-limit');
|
||||
const type=parameters(contentType);
|
||||
if(type.main!=='multipart/form-data'||Object.keys(type.values).some(name=>name!=='boundary')||!/^[0-9a-z'()+_,./:=?-]{1,70}$/i.test(type.values.boundary||''))fail('invalid-multipart');
|
||||
const boundary=Buffer.from('--'+type.values.boundary),separator=Buffer.from('\r\n--'+type.values.boundary),parts=[];
|
||||
const dispose=()=>{for(const part of parts)part.body.fill(0);};
|
||||
try{
|
||||
if(!body.subarray(0,boundary.length).equals(boundary))fail('invalid-multipart');
|
||||
let position=boundary.length;
|
||||
while(true){
|
||||
if(body.subarray(position,position+2).equals(Buffer.from('--'))){
|
||||
position+=2;if(body.subarray(position).equals(Buffer.from('\r\n')))position+=2;
|
||||
if(position!==body.length)fail('invalid-multipart');break;
|
||||
}
|
||||
if(!body.subarray(position,position+2).equals(Buffer.from('\r\n')))fail('invalid-multipart');position+=2;
|
||||
if(parts.length>=maxParts)fail('multipart-limit');
|
||||
const headerEnd=body.indexOf('\r\n\r\n',position);
|
||||
if(headerEnd<0)fail('invalid-multipart');if(headerEnd-position>maxHeaderBytes)fail('multipart-limit');
|
||||
const record=metadata(body.subarray(position,headerEnd));position=headerEnd+4;
|
||||
let end=position;
|
||||
while(true){
|
||||
end=body.indexOf(separator,end);if(end<0)fail('invalid-multipart');
|
||||
const suffix=body.subarray(end+separator.length,end+separator.length+2);
|
||||
if(suffix.equals(Buffer.from('\r\n'))||suffix.equals(Buffer.from('--')))break;
|
||||
end++;
|
||||
}
|
||||
parts.push(Object.freeze({...record,body:Buffer.from(body.subarray(position,end))}));position=end+separator.length;
|
||||
}
|
||||
return Object.freeze({coverage:Object.freeze({complete:true}),parts:Object.freeze(parts),dispose});
|
||||
}catch(error){dispose();throw error;}
|
||||
}
|
||||
module.exports={parseMultipart,MultipartError};
|
||||
Reference in new issue
Block a user