@@ -95,22 +95,22 @@ describe('type()', () => {
9595
9696 it ( 'errors on missing header' , ( ) => {
9797
98- expect ( ( ) => Content . type ( ) ) . to . throw ( ) ;
98+ expect ( ( ) => Content . type ( ) ) . to . throw ( 'Invalid content-type header' ) ;
9999 } ) ;
100100
101101 it ( 'errors on invalid header' , ( ) => {
102102
103- expect ( ( ) => Content . type ( 'application; some' ) ) . to . throw ( ) ;
103+ expect ( ( ) => Content . type ( 'application; some' ) ) . to . throw ( 'Invalid content-type header' ) ;
104104 } ) ;
105105
106106 it ( 'errors on multipart missing boundary' , ( ) => {
107107
108- expect ( ( ) => Content . type ( 'multipart/form-data' ) ) . to . throw ( ) ;
108+ expect ( ( ) => Content . type ( 'multipart/form-data' ) ) . to . throw ( 'Invalid content-type header: multipart missing boundary' ) ;
109109 } ) ;
110110
111111 it ( 'errors on multipart missing boundary (other params)' , ( ) => {
112112
113- expect ( ( ) => Content . type ( 'multipart/form-data; some=thing' ) ) . to . throw ( ) ;
113+ expect ( ( ) => Content . type ( 'multipart/form-data; some=thing' ) ) . to . throw ( 'Invalid content-type header: multipart missing boundary' ) ;
114114 } ) ;
115115
116116 it ( 'handles multiple boundary params' , ( ) => {
@@ -128,6 +128,19 @@ describe('type()', () => {
128128 Content . type ( header ) ;
129129 expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
130130 } ) ;
131+
132+ it ( 'handles trailing newline in content-type without backtracking' , ( ) => {
133+
134+ const header = 'a/b' + 'c' . repeat ( 50000 ) + '\n' ;
135+ const now = Date . now ( ) ;
136+ expect ( ( ) => Content . type ( header ) ) . to . throw ( 'Invalid content-type header' ) ;
137+ expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
138+ } ) ;
139+
140+ it ( 'errors on content-type with embedded newline' , ( ) => {
141+
142+ expect ( ( ) => Content . type ( 'application/json\n; charset=utf-8' ) ) . to . throw ( 'Invalid content-type header' ) ;
143+ } ) ;
131144} ) ;
132145
133146describe ( 'disposition()' , ( ) => {
@@ -164,7 +177,15 @@ describe('disposition()', () => {
164177
165178 const now = Date . now ( ) ;
166179 const header = `form-data; x; ${ new Array ( 5000 ) . join ( ' ' ) } ;` ;
167- expect ( ( ) => Content . disposition ( header ) ) . to . throw ( ) ;
180+ expect ( ( ) => Content . disposition ( header ) ) . to . throw ( 'Invalid content-disposition header missing name parameter' ) ;
181+ expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
182+ } ) ;
183+
184+ it ( 'handles trailing newline in content-disposition without backtracking' , ( ) => {
185+
186+ const header = 'form-data;' + ' ' . repeat ( 50000 ) + '\n' ;
187+ const now = Date . now ( ) ;
188+ expect ( ( ) => Content . disposition ( header ) ) . to . throw ( 'Invalid content-disposition header format' ) ;
168189 expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
169190 } ) ;
170191
@@ -220,4 +241,114 @@ describe('disposition()', () => {
220241 const header = 'form-data; name="__proto__"; filename=file.jpg' ;
221242 expect ( ( ) => Content . disposition ( header ) ) . to . throw ( 'Invalid content-disposition header format includes invalid parameters' ) ;
222243 } ) ;
244+
245+ it ( 'handles ReDoS exploit (polynomial backtracking via crafted commas and spaces)' , ( ) => {
246+
247+ const N = 4000 ;
248+ const header = 'form-data;' + '=' + ',' . repeat ( N ) + '"=' + ' ' . repeat ( N ) + '= "' ;
249+ const now = Date . now ( ) ;
250+ expect ( ( ) => Content . disposition ( header ) ) . to . throw ( 'Invalid content-disposition header missing name parameter' ) ;
251+ expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
252+ } ) ;
253+
254+ it ( 'handles large number of spaces around equals' , ( ) => {
255+
256+ const now = Date . now ( ) ;
257+ const header = `form-data; name${ ' ' . repeat ( 10000 ) } =${ ' ' . repeat ( 10000 ) } file` ;
258+ Content . disposition ( header ) ;
259+ expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
260+ } ) ;
261+
262+ it ( 'handles large number of parameters' , ( ) => {
263+
264+ const params = Array . from ( { length : 1000 } , ( _ , i ) => `p${ i } =v${ i } ` ) . join ( '; ' ) ;
265+ const header = `form-data; name="file"; ${ params } ` ;
266+ const now = Date . now ( ) ;
267+ Content . disposition ( header ) ;
268+ expect ( Date . now ( ) - now ) . to . be . below ( 100 ) ;
269+ } ) ;
270+
271+ it ( 'parses header with spaces around equals' , ( ) => {
272+
273+ const header = 'form-data; name = "file" ; filename = file.jpg' ;
274+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'file.jpg' } ) ;
275+ } ) ;
276+
277+ it ( 'parses header with empty token value' , ( ) => {
278+
279+ const header = 'form-data; name="file"; filename=' ;
280+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : '' } ) ;
281+ } ) ;
282+
283+ it ( 'parses header with multiple parameters' , ( ) => {
284+
285+ const header = 'form-data; name="file"; filename="test.jpg"; size=1234' ;
286+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' , size : '1234' } ) ;
287+ } ) ;
288+
289+ it ( 'parses header with ext-value and regular params' , ( ) => {
290+
291+ const header = 'form-data; name="file"; filename*=utf-8\'en\'my%20file.jpg; size=999' ;
292+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'my file.jpg' , size : '999' } ) ;
293+ } ) ;
294+
295+ it ( 'parses header with no trailing semicolon' , ( ) => {
296+
297+ const header = 'form-data; name="file"; filename=test.jpg' ;
298+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
299+ } ) ;
300+
301+ it ( 'parses header with trailing semicolon' , ( ) => {
302+
303+ const header = 'form-data; name="file"; filename=test.jpg;' ;
304+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
305+ } ) ;
306+
307+ it ( 'parses header with extra whitespace between params' , ( ) => {
308+
309+ const header = 'form-data; name="file" ; filename=test.jpg ; size=100' ;
310+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' , size : '100' } ) ;
311+ } ) ;
312+
313+ it ( 'parses header with hyphenated parameter name' , ( ) => {
314+
315+ const header = 'form-data; name="file"; file-name=test.jpg' ;
316+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , 'file-name' : 'test.jpg' } ) ;
317+ } ) ;
318+
319+ it ( 'parses header with dotted parameter name' , ( ) => {
320+
321+ const header = 'form-data; name="file"; file.name=test.jpg' ;
322+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , 'file.name' : 'test.jpg' } ) ;
323+ } ) ;
324+
325+ it ( 'parses header with ext-value without language tag' , ( ) => {
326+
327+ const header = 'form-data; name="file"; filename*=utf-8\'\'my%20file.jpg' ;
328+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'my file.jpg' } ) ;
329+ } ) ;
330+
331+ it ( 'parses params even when followed by non-param garbage' , ( ) => {
332+
333+ const header = 'form-data; name="file"; filename=test.jpg GARBAGE' ;
334+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
335+ } ) ;
336+
337+ it ( 'parses params when a key without value follows' , ( ) => {
338+
339+ const header = 'form-data; name="file"; filename=test.jpg; notaparam' ;
340+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
341+ } ) ;
342+
343+ it ( 'parses header with trailing whitespace after last param' , ( ) => {
344+
345+ const header = 'form-data; name="file"; filename=test.jpg ' ;
346+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
347+ } ) ;
348+
349+ it ( 'parses header with trailing whitespace after last quoted param' , ( ) => {
350+
351+ const header = 'form-data; name="file"; filename="test.jpg" ' ;
352+ expect ( Content . disposition ( header ) ) . to . equal ( { name : 'file' , filename : 'test.jpg' } ) ;
353+ } ) ;
223354} ) ;
0 commit comments