3 var assert
= require('assert')
4 var asyncHooks
= tryRequire('async_hooks')
5 var Buffer
= require('safe-buffer').Buffer
6 var express
= require('..')
7 var request
= require('supertest')
9 var describeAsyncHooks
= typeof asyncHooks
.AsyncLocalStorage
=== 'function'
13 describe('express.urlencoded()', function () {
15 this.app
= createApp()
18 it('should parse x-www-form-urlencoded', function (done
) {
21 .set('Content-Type', 'application/x-www-form-urlencoded')
23 .expect(200, '{"user":"tobi"}', done
)
26 it('should 400 when invalid content-length', function (done
) {
29 app
.use(function (req
, res
, next
) {
30 req
.headers
['content-length'] = '20' // bad length
34 app
.use(express
.urlencoded())
36 app
.post('/', function (req
, res
) {
42 .set('Content-Type', 'application/x-www-form-urlencoded')
44 .expect(400, /content length/, done
)
47 it('should handle Content-Length: 0', function (done
) {
50 .set('Content-Type', 'application/x-www-form-urlencoded')
51 .set('Content-Length', '0')
53 .expect(200, '{}', done
)
56 it('should handle empty message-body', function (done
) {
57 request(createApp({ limit
: '1kb' }))
59 .set('Content-Type', 'application/x-www-form-urlencoded')
60 .set('Transfer-Encoding', 'chunked')
62 .expect(200, '{}', done
)
65 it('should handle duplicated middleware', function (done
) {
68 app
.use(express
.urlencoded())
69 app
.use(express
.urlencoded())
71 app
.post('/', function (req
, res
) {
77 .set('Content-Type', 'application/x-www-form-urlencoded')
79 .expect(200, '{"user":"tobi"}', done
)
82 it('should not parse extended syntax', function (done
) {
85 .set('Content-Type', 'application/x-www-form-urlencoded')
86 .send('user[name][first]=Tobi')
87 .expect(200, '{"user[name][first]":"Tobi"}', done
)
90 describe('with extended option', function () {
91 describe('when false', function () {
93 this.app
= createApp({ extended
: false })
96 it('should not parse extended syntax', function (done
) {
99 .set('Content-Type', 'application/x-www-form-urlencoded')
100 .send('user[name][first]=Tobi')
101 .expect(200, '{"user[name][first]":"Tobi"}', done
)
104 it('should parse multiple key instances', function (done
) {
107 .set('Content-Type', 'application/x-www-form-urlencoded')
108 .send('user=Tobi&user=Loki')
109 .expect(200, '{"user":["Tobi","Loki"]}', done
)
113 describe('when true', function () {
115 this.app
= createApp({ extended
: true })
118 it('should parse multiple key instances', function (done
) {
121 .set('Content-Type', 'application/x-www-form-urlencoded')
122 .send('user=Tobi&user=Loki')
123 .expect(200, '{"user":["Tobi","Loki"]}', done
)
126 it('should parse extended syntax', function (done
) {
129 .set('Content-Type', 'application/x-www-form-urlencoded')
130 .send('user[name][first]=Tobi')
131 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done
)
134 it('should parse parameters with dots', function (done
) {
137 .set('Content-Type', 'application/x-www-form-urlencoded')
138 .send('user.name=Tobi')
139 .expect(200, '{"user.name":"Tobi"}', done
)
142 it('should parse fully-encoded extended syntax', function (done
) {
145 .set('Content-Type', 'application/x-www-form-urlencoded')
146 .send('user%5Bname%5D%5Bfirst%5D=Tobi')
147 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done
)
150 it('should parse array index notation', function (done
) {
153 .set('Content-Type', 'application/x-www-form-urlencoded')
154 .send('foo[0]=bar&foo[1]=baz')
155 .expect(200, '{"foo":["bar","baz"]}', done
)
158 it('should parse array index notation with large array', function (done
) {
161 for (var i
= 1; i
< 500; i
++) {
162 str
+= '&f[' + i
+ ']=' + i
.toString(16)
167 .set('Content-Type', 'application/x-www-form-urlencoded')
169 .expect(function (res
) {
170 var obj
= JSON
.parse(res
.text
)
171 assert
.strictEqual(Object
.keys(obj
).length
, 1)
172 assert
.strictEqual(Array
.isArray(obj
.f
), true)
173 assert
.strictEqual(obj
.f
.length
, 500)
178 it('should parse array of objects syntax', function (done
) {
181 .set('Content-Type', 'application/x-www-form-urlencoded')
182 .send('foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!')
183 .expect(200, '{"foo":[{"bar":"baz","fizz":"buzz"},"done!"]}', done
)
186 it('should parse deep object', function (done
) {
189 for (var i
= 0; i
< 32; i
++) {
197 .set('Content-Type', 'application/x-www-form-urlencoded')
199 .expect(function (res
) {
200 var obj
= JSON
.parse(res
.text
)
201 assert
.strictEqual(Object
.keys(obj
).length
, 1)
202 assert
.strictEqual(typeof obj
.foo
, 'object')
206 while ((ref
= ref
.p
)) { depth
++ }
207 assert
.strictEqual(depth
, 32)
214 describe('with inflate option', function () {
215 describe('when false', function () {
217 this.app
= createApp({ inflate
: false })
220 it('should not accept content-encoding', function (done
) {
221 var test
= request(this.app
).post('/')
222 test
.set('Content-Encoding', 'gzip')
223 test
.set('Content-Type', 'application/x-www-form-urlencoded')
224 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
225 test
.expect(415, '[encoding.unsupported] content encoding unsupported', done
)
229 describe('when true', function () {
231 this.app
= createApp({ inflate
: true })
234 it('should accept content-encoding', function (done
) {
235 var test
= request(this.app
).post('/')
236 test
.set('Content-Encoding', 'gzip')
237 test
.set('Content-Type', 'application/x-www-form-urlencoded')
238 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
239 test
.expect(200, '{"name":"论"}', done
)
244 describe('with limit option', function () {
245 it('should 413 when over limit with Content-Length', function (done
) {
246 var buf
= Buffer
.alloc(1024, '.')
247 request(createApp({ limit
: '1kb' }))
249 .set('Content-Type', 'application/x-www-form-urlencoded')
250 .set('Content-Length', '1028')
251 .send('str=' + buf
.toString())
255 it('should 413 when over limit with chunked encoding', function (done
) {
256 var app
= createApp({ limit
: '1kb' })
257 var buf
= Buffer
.alloc(1024, '.')
258 var test
= request(app
).post('/')
259 test
.set('Content-Type', 'application/x-www-form-urlencoded')
260 test
.set('Transfer-Encoding', 'chunked')
262 test
.write(buf
.toString())
263 test
.expect(413, done
)
266 it('should 413 when inflated body over limit', function (done
) {
267 var app
= createApp({ limit
: '1kb' })
268 var test
= request(app
).post('/')
269 test
.set('Content-Encoding', 'gzip')
270 test
.set('Content-Type', 'application/x-www-form-urlencoded')
271 test
.write(Buffer
.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f9204040000', 'hex'))
272 test
.expect(413, done
)
275 it('should accept number of bytes', function (done
) {
276 var buf
= Buffer
.alloc(1024, '.')
277 request(createApp({ limit
: 1024 }))
279 .set('Content-Type', 'application/x-www-form-urlencoded')
280 .send('str=' + buf
.toString())
284 it('should not change when options altered', function (done
) {
285 var buf
= Buffer
.alloc(1024, '.')
286 var options
= { limit
: '1kb' }
287 var app
= createApp(options
)
289 options
.limit
= '100kb'
293 .set('Content-Type', 'application/x-www-form-urlencoded')
294 .send('str=' + buf
.toString())
298 it('should not hang response', function (done
) {
299 var app
= createApp({ limit
: '8kb' })
300 var buf
= Buffer
.alloc(10240, '.')
301 var test
= request(app
).post('/')
302 test
.set('Content-Type', 'application/x-www-form-urlencoded')
306 test
.expect(413, done
)
309 it('should not error when inflating', function (done
) {
310 var app
= createApp({ limit
: '1kb' })
311 var test
= request(app
).post('/')
312 test
.set('Content-Encoding', 'gzip')
313 test
.set('Content-Type', 'application/x-www-form-urlencoded')
314 test
.write(Buffer
.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f92040400', 'hex'))
315 test
.expect(413, done
)
319 describe('with parameterLimit option', function () {
320 describe('with extended: false', function () {
321 it('should reject 0', function () {
322 assert
.throws(createApp
.bind(null, { extended
: false, parameterLimit
: 0 }),
323 /TypeError: option parameterLimit must be a positive number/)
326 it('should reject string', function () {
327 assert
.throws(createApp
.bind(null, { extended
: false, parameterLimit
: 'beep' }),
328 /TypeError: option parameterLimit must be a positive number/)
331 it('should 413 if over limit', function (done
) {
332 request(createApp({ extended
: false, parameterLimit
: 10 }))
334 .set('Content-Type', 'application/x-www-form-urlencoded')
335 .send(createManyParams(11))
336 .expect(413, '[parameters.too.many] too many parameters', done
)
339 it('should work when at the limit', function (done
) {
340 request(createApp({ extended
: false, parameterLimit
: 10 }))
342 .set('Content-Type', 'application/x-www-form-urlencoded')
343 .send(createManyParams(10))
344 .expect(expectKeyCount(10))
348 it('should work if number is floating point', function (done
) {
349 request(createApp({ extended
: false, parameterLimit
: 10.1 }))
351 .set('Content-Type', 'application/x-www-form-urlencoded')
352 .send(createManyParams(11))
353 .expect(413, /too many parameters/, done
)
356 it('should work with large limit', function (done
) {
357 request(createApp({ extended
: false, parameterLimit
: 5000 }))
359 .set('Content-Type', 'application/x-www-form-urlencoded')
360 .send(createManyParams(5000))
361 .expect(expectKeyCount(5000))
365 it('should work with Infinity limit', function (done
) {
366 request(createApp({ extended
: false, parameterLimit
: Infinity
}))
368 .set('Content-Type', 'application/x-www-form-urlencoded')
369 .send(createManyParams(10000))
370 .expect(expectKeyCount(10000))
375 describe('with extended: true', function () {
376 it('should reject 0', function () {
377 assert
.throws(createApp
.bind(null, { extended
: true, parameterLimit
: 0 }),
378 /TypeError: option parameterLimit must be a positive number/)
381 it('should reject string', function () {
382 assert
.throws(createApp
.bind(null, { extended
: true, parameterLimit
: 'beep' }),
383 /TypeError: option parameterLimit must be a positive number/)
386 it('should 413 if over limit', function (done
) {
387 request(createApp({ extended
: true, parameterLimit
: 10 }))
389 .set('Content-Type', 'application/x-www-form-urlencoded')
390 .send(createManyParams(11))
391 .expect(413, '[parameters.too.many] too many parameters', done
)
394 it('should work when at the limit', function (done
) {
395 request(createApp({ extended
: true, parameterLimit
: 10 }))
397 .set('Content-Type', 'application/x-www-form-urlencoded')
398 .send(createManyParams(10))
399 .expect(expectKeyCount(10))
403 it('should work if number is floating point', function (done
) {
404 request(createApp({ extended
: true, parameterLimit
: 10.1 }))
406 .set('Content-Type', 'application/x-www-form-urlencoded')
407 .send(createManyParams(11))
408 .expect(413, /too many parameters/, done
)
411 it('should work with large limit', function (done
) {
412 request(createApp({ extended
: true, parameterLimit
: 5000 }))
414 .set('Content-Type', 'application/x-www-form-urlencoded')
415 .send(createManyParams(5000))
416 .expect(expectKeyCount(5000))
420 it('should work with Infinity limit', function (done
) {
421 request(createApp({ extended
: true, parameterLimit
: Infinity
}))
423 .set('Content-Type', 'application/x-www-form-urlencoded')
424 .send(createManyParams(10000))
425 .expect(expectKeyCount(10000))
431 describe('with type option', function () {
432 describe('when "application/vnd.x-www-form-urlencoded"', function () {
434 this.app
= createApp({ type
: 'application/vnd.x-www-form-urlencoded' })
437 it('should parse for custom type', function (done
) {
440 .set('Content-Type', 'application/vnd.x-www-form-urlencoded')
442 .expect(200, '{"user":"tobi"}', done
)
445 it('should ignore standard type', function (done
) {
448 .set('Content-Type', 'application/x-www-form-urlencoded')
450 .expect(200, '', done
)
454 describe('when ["urlencoded", "application/x-pairs"]', function () {
456 this.app
= createApp({
457 type
: ['urlencoded', 'application/x-pairs']
461 it('should parse "application/x-www-form-urlencoded"', function (done
) {
464 .set('Content-Type', 'application/x-www-form-urlencoded')
466 .expect(200, '{"user":"tobi"}', done
)
469 it('should parse "application/x-pairs"', function (done
) {
472 .set('Content-Type', 'application/x-pairs')
474 .expect(200, '{"user":"tobi"}', done
)
477 it('should ignore application/x-foo', function (done
) {
480 .set('Content-Type', 'application/x-foo')
482 .expect(200, '', done
)
486 describe('when a function', function () {
487 it('should parse when truthy value returned', function (done
) {
488 var app
= createApp({ type
: accept
})
490 function accept (req
) {
491 return req
.headers
['content-type'] === 'application/vnd.something'
496 .set('Content-Type', 'application/vnd.something')
498 .expect(200, '{"user":"tobi"}', done
)
501 it('should work without content-type', function (done
) {
502 var app
= createApp({ type
: accept
})
504 function accept (req
) {
508 var test
= request(app
).post('/')
509 test
.write('user=tobi')
510 test
.expect(200, '{"user":"tobi"}', done
)
513 it('should not invoke without a body', function (done
) {
514 var app
= createApp({ type
: accept
})
516 function accept (req
) {
517 throw new Error('oops!')
527 describe('with verify option', function () {
528 it('should assert value if function', function () {
529 assert
.throws(createApp
.bind(null, { verify
: 'lol' }),
530 /TypeError: option verify must be function/)
533 it('should error from verify', function (done
) {
534 var app
= createApp({
535 verify: function (req
, res
, buf
) {
536 if (buf
[0] === 0x20) throw new Error('no leading space')
542 .set('Content-Type', 'application/x-www-form-urlencoded')
544 .expect(403, '[entity.verify.failed] no leading space', done
)
547 it('should allow custom codes', function (done
) {
548 var app
= createApp({
549 verify: function (req
, res
, buf
) {
550 if (buf
[0] !== 0x20) return
551 var err
= new Error('no leading space')
559 .set('Content-Type', 'application/x-www-form-urlencoded')
561 .expect(400, '[entity.verify.failed] no leading space', done
)
564 it('should allow custom type', function (done
) {
565 var app
= createApp({
566 verify: function (req
, res
, buf
) {
567 if (buf
[0] !== 0x20) return
568 var err
= new Error('no leading space')
576 .set('Content-Type', 'application/x-www-form-urlencoded')
578 .expect(403, '[foo.bar] no leading space', done
)
581 it('should allow pass-through', function (done
) {
582 var app
= createApp({
583 verify: function (req
, res
, buf
) {
584 if (buf
[0] === 0x5b) throw new Error('no arrays')
590 .set('Content-Type', 'application/x-www-form-urlencoded')
592 .expect(200, '{"user":"tobi"}', done
)
595 it('should 415 on unknown charset prior to verify', function (done
) {
596 var app
= createApp({
597 verify: function (req
, res
, buf
) {
598 throw new Error('unexpected verify call')
602 var test
= request(app
).post('/')
603 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=x-bogus')
604 test
.write(Buffer
.from('00000000', 'hex'))
605 test
.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done
)
609 describeAsyncHooks('async local storage', function () {
612 var store
= { foo
: 'bar' }
614 app
.use(function (req
, res
, next
) {
615 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
616 req
.asyncLocalStorage
.run(store
, next
)
619 app
.use(express
.urlencoded())
621 app
.use(function (req
, res
, next
) {
622 var local
= req
.asyncLocalStorage
.getStore()
625 res
.setHeader('x-store-foo', String(local
.foo
))
631 app
.use(function (err
, req
, res
, next
) {
632 var local
= req
.asyncLocalStorage
.getStore()
635 res
.setHeader('x-store-foo', String(local
.foo
))
638 res
.status(err
.status
|| 500)
639 res
.send('[' + err
.type
+ '] ' + err
.message
)
642 app
.post('/', function (req
, res
) {
649 it('should presist store', function (done
) {
652 .set('Content-Type', 'application/x-www-form-urlencoded')
655 .expect('x-store-foo', 'bar')
656 .expect('{"user":"tobi"}')
660 it('should presist store when unmatched content-type', function (done
) {
663 .set('Content-Type', 'application/fizzbuzz')
666 .expect('x-store-foo', 'bar')
670 it('should presist store when inflated', function (done
) {
671 var test
= request(this.app
).post('/')
672 test
.set('Content-Encoding', 'gzip')
673 test
.set('Content-Type', 'application/x-www-form-urlencoded')
674 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
676 test
.expect('x-store-foo', 'bar')
677 test
.expect('{"name":"论"}')
681 it('should presist store when inflate error', function (done
) {
682 var test
= request(this.app
).post('/')
683 test
.set('Content-Encoding', 'gzip')
684 test
.set('Content-Type', 'application/x-www-form-urlencoded')
685 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad6080000', 'hex'))
687 test
.expect('x-store-foo', 'bar')
691 it('should presist store when limit exceeded', function (done
) {
694 .set('Content-Type', 'application/x-www-form-urlencoded')
695 .send('user=' + Buffer
.alloc(1024 * 100, '.').toString())
697 .expect('x-store-foo', 'bar')
702 describe('charset', function () {
704 this.app
= createApp()
707 it('should parse utf-8', function (done
) {
708 var test
= request(this.app
).post('/')
709 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
710 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
711 test
.expect(200, '{"name":"论"}', done
)
714 it('should parse when content-length != char length', function (done
) {
715 var test
= request(this.app
).post('/')
716 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
717 test
.set('Content-Length', '7')
718 test
.write(Buffer
.from('746573743dc3a5', 'hex'))
719 test
.expect(200, '{"test":"å"}', done
)
722 it('should default to utf-8', function (done
) {
723 var test
= request(this.app
).post('/')
724 test
.set('Content-Type', 'application/x-www-form-urlencoded')
725 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
726 test
.expect(200, '{"name":"论"}', done
)
729 it('should fail on unknown charset', function (done
) {
730 var test
= request(this.app
).post('/')
731 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=koi8-r')
732 test
.write(Buffer
.from('6e616d653dcec5d4', 'hex'))
733 test
.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done
)
737 describe('encoding', function () {
739 this.app
= createApp({ limit
: '10kb' })
742 it('should parse without encoding', function (done
) {
743 var test
= request(this.app
).post('/')
744 test
.set('Content-Type', 'application/x-www-form-urlencoded')
745 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
746 test
.expect(200, '{"name":"论"}', done
)
749 it('should support identity encoding', function (done
) {
750 var test
= request(this.app
).post('/')
751 test
.set('Content-Encoding', 'identity')
752 test
.set('Content-Type', 'application/x-www-form-urlencoded')
753 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
754 test
.expect(200, '{"name":"论"}', done
)
757 it('should support gzip encoding', function (done
) {
758 var test
= request(this.app
).post('/')
759 test
.set('Content-Encoding', 'gzip')
760 test
.set('Content-Type', 'application/x-www-form-urlencoded')
761 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
762 test
.expect(200, '{"name":"论"}', done
)
765 it('should support deflate encoding', function (done
) {
766 var test
= request(this.app
).post('/')
767 test
.set('Content-Encoding', 'deflate')
768 test
.set('Content-Type', 'application/x-www-form-urlencoded')
769 test
.write(Buffer
.from('789ccb4bcc4db57db16e17001068042f', 'hex'))
770 test
.expect(200, '{"name":"论"}', done
)
773 it('should be case-insensitive', function (done
) {
774 var test
= request(this.app
).post('/')
775 test
.set('Content-Encoding', 'GZIP')
776 test
.set('Content-Type', 'application/x-www-form-urlencoded')
777 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
778 test
.expect(200, '{"name":"论"}', done
)
781 it('should 415 on unknown encoding', function (done
) {
782 var test
= request(this.app
).post('/')
783 test
.set('Content-Encoding', 'nulls')
784 test
.set('Content-Type', 'application/x-www-form-urlencoded')
785 test
.write(Buffer
.from('000000000000', 'hex'))
786 test
.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done
)
791 function createManyParams (count
) {
800 for (var i
= 1; i
< count
; i
++) {
801 var n
= i
.toString(36)
802 str
+= '&' + n
+ '=' + n
808 function createApp (options
) {
811 app
.use(express
.urlencoded(options
))
813 app
.use(function (err
, req
, res
, next
) {
814 res
.status(err
.status
|| 500)
815 res
.send(String(req
.headers
['x-error-property']
816 ? err
[req
.headers
['x-error-property']]
817 : ('[' + err
.type
+ '] ' + err
.message
)))
820 app
.post('/', function (req
, res
) {
827 function expectKeyCount (count
) {
828 return function (res
) {
829 assert
.strictEqual(Object
.keys(JSON
.parse(res
.text
)).length
, count
)
833 function tryRequire (name
) {