3 var assert
= require('assert')
4 var asyncHooks
= tryRequire('async_hooks')
5 var express
= require('..')
6 var request
= require('supertest')
8 var describeAsyncHooks
= typeof asyncHooks
.AsyncLocalStorage
=== 'function'
12 describe('express.urlencoded()', function () {
14 this.app
= createApp()
17 it('should parse x-www-form-urlencoded', function (done
) {
20 .set('Content-Type', 'application/x-www-form-urlencoded')
22 .expect(200, '{"user":"tobi"}', done
)
25 it('should 400 when invalid content-length', function (done
) {
28 app
.use(function (req
, res
, next
) {
29 req
.headers
['content-length'] = '20' // bad length
33 app
.use(express
.urlencoded())
35 app
.post('/', function (req
, res
) {
41 .set('Content-Type', 'application/x-www-form-urlencoded')
43 .expect(400, /content length/, done
)
46 it('should handle Content-Length: 0', function (done
) {
49 .set('Content-Type', 'application/x-www-form-urlencoded')
50 .set('Content-Length', '0')
52 .expect(200, '{}', done
)
55 it('should handle empty message-body', function (done
) {
56 request(createApp({ limit
: '1kb' }))
58 .set('Content-Type', 'application/x-www-form-urlencoded')
59 .set('Transfer-Encoding', 'chunked')
61 .expect(200, '{}', done
)
64 it('should handle duplicated middleware', function (done
) {
67 app
.use(express
.urlencoded())
68 app
.use(express
.urlencoded())
70 app
.post('/', function (req
, res
) {
76 .set('Content-Type', 'application/x-www-form-urlencoded')
78 .expect(200, '{"user":"tobi"}', done
)
81 it('should not parse extended syntax', function (done
) {
84 .set('Content-Type', 'application/x-www-form-urlencoded')
85 .send('user[name][first]=Tobi')
86 .expect(200, '{"user[name][first]":"Tobi"}', done
)
89 describe('with extended option', function () {
90 describe('when false', function () {
92 this.app
= createApp({ extended
: false })
95 it('should not parse extended syntax', function (done
) {
98 .set('Content-Type', 'application/x-www-form-urlencoded')
99 .send('user[name][first]=Tobi')
100 .expect(200, '{"user[name][first]":"Tobi"}', done
)
103 it('should parse multiple key instances', function (done
) {
106 .set('Content-Type', 'application/x-www-form-urlencoded')
107 .send('user=Tobi&user=Loki')
108 .expect(200, '{"user":["Tobi","Loki"]}', done
)
112 describe('when true', function () {
114 this.app
= createApp({ extended
: true })
117 it('should parse multiple key instances', function (done
) {
120 .set('Content-Type', 'application/x-www-form-urlencoded')
121 .send('user=Tobi&user=Loki')
122 .expect(200, '{"user":["Tobi","Loki"]}', done
)
125 it('should parse extended syntax', function (done
) {
128 .set('Content-Type', 'application/x-www-form-urlencoded')
129 .send('user[name][first]=Tobi')
130 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done
)
133 it('should parse parameters with dots', function (done
) {
136 .set('Content-Type', 'application/x-www-form-urlencoded')
137 .send('user.name=Tobi')
138 .expect(200, '{"user.name":"Tobi"}', done
)
141 it('should parse fully-encoded extended syntax', function (done
) {
144 .set('Content-Type', 'application/x-www-form-urlencoded')
145 .send('user%5Bname%5D%5Bfirst%5D=Tobi')
146 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done
)
149 it('should parse array index notation', function (done
) {
152 .set('Content-Type', 'application/x-www-form-urlencoded')
153 .send('foo[0]=bar&foo[1]=baz')
154 .expect(200, '{"foo":["bar","baz"]}', done
)
157 it('should parse array index notation with large array', function (done
) {
160 for (var i
= 1; i
< 500; i
++) {
161 str
+= '&f[' + i
+ ']=' + i
.toString(16)
166 .set('Content-Type', 'application/x-www-form-urlencoded')
168 .expect(function (res
) {
169 var obj
= JSON
.parse(res
.text
)
170 assert
.strictEqual(Object
.keys(obj
).length
, 1)
171 assert
.strictEqual(Array
.isArray(obj
.f
), true)
172 assert
.strictEqual(obj
.f
.length
, 500)
177 it('should parse array of objects syntax', function (done
) {
180 .set('Content-Type', 'application/x-www-form-urlencoded')
181 .send('foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!')
182 .expect(200, '{"foo":[{"bar":"baz","fizz":"buzz"},"done!"]}', done
)
185 it('should parse deep object', function (done
) {
188 for (var i
= 0; i
< 32; i
++) {
196 .set('Content-Type', 'application/x-www-form-urlencoded')
198 .expect(function (res
) {
199 var obj
= JSON
.parse(res
.text
)
200 assert
.strictEqual(Object
.keys(obj
).length
, 1)
201 assert
.strictEqual(typeof obj
.foo
, 'object')
205 while ((ref
= ref
.p
)) { depth
++ }
206 assert
.strictEqual(depth
, 32)
213 describe('with inflate option', function () {
214 describe('when false', function () {
216 this.app
= createApp({ inflate
: false })
219 it('should not accept content-encoding', function (done
) {
220 var test
= request(this.app
).post('/')
221 test
.set('Content-Encoding', 'gzip')
222 test
.set('Content-Type', 'application/x-www-form-urlencoded')
223 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
224 test
.expect(415, '[encoding.unsupported] content encoding unsupported', done
)
228 describe('when true', function () {
230 this.app
= createApp({ inflate
: true })
233 it('should accept content-encoding', function (done
) {
234 var test
= request(this.app
).post('/')
235 test
.set('Content-Encoding', 'gzip')
236 test
.set('Content-Type', 'application/x-www-form-urlencoded')
237 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
238 test
.expect(200, '{"name":"论"}', done
)
243 describe('with limit option', function () {
244 it('should 413 when over limit with Content-Length', function (done
) {
245 var buf
= Buffer
.alloc(1024, '.')
246 request(createApp({ limit
: '1kb' }))
248 .set('Content-Type', 'application/x-www-form-urlencoded')
249 .set('Content-Length', '1028')
250 .send('str=' + buf
.toString())
254 it('should 413 when over limit with chunked encoding', function (done
) {
255 var app
= createApp({ limit
: '1kb' })
256 var buf
= Buffer
.alloc(1024, '.')
257 var test
= request(app
).post('/')
258 test
.set('Content-Type', 'application/x-www-form-urlencoded')
259 test
.set('Transfer-Encoding', 'chunked')
261 test
.write(buf
.toString())
262 test
.expect(413, done
)
265 it('should 413 when inflated body over limit', function (done
) {
266 var app
= createApp({ limit
: '1kb' })
267 var test
= request(app
).post('/')
268 test
.set('Content-Encoding', 'gzip')
269 test
.set('Content-Type', 'application/x-www-form-urlencoded')
270 test
.write(Buffer
.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f9204040000', 'hex'))
271 test
.expect(413, done
)
274 it('should accept number of bytes', function (done
) {
275 var buf
= Buffer
.alloc(1024, '.')
276 request(createApp({ limit
: 1024 }))
278 .set('Content-Type', 'application/x-www-form-urlencoded')
279 .send('str=' + buf
.toString())
283 it('should not change when options altered', function (done
) {
284 var buf
= Buffer
.alloc(1024, '.')
285 var options
= { limit
: '1kb' }
286 var app
= createApp(options
)
288 options
.limit
= '100kb'
292 .set('Content-Type', 'application/x-www-form-urlencoded')
293 .send('str=' + buf
.toString())
297 it('should not hang response', function (done
) {
298 var app
= createApp({ limit
: '8kb' })
299 var buf
= Buffer
.alloc(10240, '.')
300 var test
= request(app
).post('/')
301 test
.set('Content-Type', 'application/x-www-form-urlencoded')
305 test
.expect(413, done
)
308 it('should not error when inflating', function (done
) {
309 var app
= createApp({ limit
: '1kb' })
310 var test
= request(app
).post('/')
311 test
.set('Content-Encoding', 'gzip')
312 test
.set('Content-Type', 'application/x-www-form-urlencoded')
313 test
.write(Buffer
.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f92040400', 'hex'))
314 test
.expect(413, done
)
318 describe('with parameterLimit option', function () {
319 describe('with extended: false', function () {
320 it('should reject 0', function () {
321 assert
.throws(createApp
.bind(null, { extended
: false, parameterLimit
: 0 }),
322 /TypeError: option parameterLimit must be a positive number/)
325 it('should reject string', function () {
326 assert
.throws(createApp
.bind(null, { extended
: false, parameterLimit
: 'beep' }),
327 /TypeError: option parameterLimit must be a positive number/)
330 it('should 413 if over limit', function (done
) {
331 request(createApp({ extended
: false, parameterLimit
: 10 }))
333 .set('Content-Type', 'application/x-www-form-urlencoded')
334 .send(createManyParams(11))
335 .expect(413, '[parameters.too.many] too many parameters', done
)
338 it('should work when at the limit', function (done
) {
339 request(createApp({ extended
: false, parameterLimit
: 10 }))
341 .set('Content-Type', 'application/x-www-form-urlencoded')
342 .send(createManyParams(10))
343 .expect(expectKeyCount(10))
347 it('should work if number is floating point', function (done
) {
348 request(createApp({ extended
: false, parameterLimit
: 10.1 }))
350 .set('Content-Type', 'application/x-www-form-urlencoded')
351 .send(createManyParams(11))
352 .expect(413, /too many parameters/, done
)
355 it('should work with large limit', function (done
) {
356 request(createApp({ extended
: false, parameterLimit
: 5000 }))
358 .set('Content-Type', 'application/x-www-form-urlencoded')
359 .send(createManyParams(5000))
360 .expect(expectKeyCount(5000))
364 it('should work with Infinity limit', function (done
) {
365 request(createApp({ extended
: false, parameterLimit
: Infinity
}))
367 .set('Content-Type', 'application/x-www-form-urlencoded')
368 .send(createManyParams(10000))
369 .expect(expectKeyCount(10000))
374 describe('with extended: true', function () {
375 it('should reject 0', function () {
376 assert
.throws(createApp
.bind(null, { extended
: true, parameterLimit
: 0 }),
377 /TypeError: option parameterLimit must be a positive number/)
380 it('should reject string', function () {
381 assert
.throws(createApp
.bind(null, { extended
: true, parameterLimit
: 'beep' }),
382 /TypeError: option parameterLimit must be a positive number/)
385 it('should 413 if over limit', function (done
) {
386 request(createApp({ extended
: true, parameterLimit
: 10 }))
388 .set('Content-Type', 'application/x-www-form-urlencoded')
389 .send(createManyParams(11))
390 .expect(413, '[parameters.too.many] too many parameters', done
)
393 it('should work when at the limit', function (done
) {
394 request(createApp({ extended
: true, parameterLimit
: 10 }))
396 .set('Content-Type', 'application/x-www-form-urlencoded')
397 .send(createManyParams(10))
398 .expect(expectKeyCount(10))
402 it('should work if number is floating point', function (done
) {
403 request(createApp({ extended
: true, parameterLimit
: 10.1 }))
405 .set('Content-Type', 'application/x-www-form-urlencoded')
406 .send(createManyParams(11))
407 .expect(413, /too many parameters/, done
)
410 it('should work with large limit', function (done
) {
411 request(createApp({ extended
: true, parameterLimit
: 5000 }))
413 .set('Content-Type', 'application/x-www-form-urlencoded')
414 .send(createManyParams(5000))
415 .expect(expectKeyCount(5000))
419 it('should work with Infinity limit', function (done
) {
420 request(createApp({ extended
: true, parameterLimit
: Infinity
}))
422 .set('Content-Type', 'application/x-www-form-urlencoded')
423 .send(createManyParams(10000))
424 .expect(expectKeyCount(10000))
430 describe('with type option', function () {
431 describe('when "application/vnd.x-www-form-urlencoded"', function () {
433 this.app
= createApp({ type
: 'application/vnd.x-www-form-urlencoded' })
436 it('should parse for custom type', function (done
) {
439 .set('Content-Type', 'application/vnd.x-www-form-urlencoded')
441 .expect(200, '{"user":"tobi"}', done
)
444 it('should ignore standard type', function (done
) {
447 .set('Content-Type', 'application/x-www-form-urlencoded')
449 .expect(200, '', done
)
453 describe('when ["urlencoded", "application/x-pairs"]', function () {
455 this.app
= createApp({
456 type
: ['urlencoded', 'application/x-pairs']
460 it('should parse "application/x-www-form-urlencoded"', function (done
) {
463 .set('Content-Type', 'application/x-www-form-urlencoded')
465 .expect(200, '{"user":"tobi"}', done
)
468 it('should parse "application/x-pairs"', function (done
) {
471 .set('Content-Type', 'application/x-pairs')
473 .expect(200, '{"user":"tobi"}', done
)
476 it('should ignore application/x-foo', function (done
) {
479 .set('Content-Type', 'application/x-foo')
481 .expect(200, '', done
)
485 describe('when a function', function () {
486 it('should parse when truthy value returned', function (done
) {
487 var app
= createApp({ type
: accept
})
489 function accept (req
) {
490 return req
.headers
['content-type'] === 'application/vnd.something'
495 .set('Content-Type', 'application/vnd.something')
497 .expect(200, '{"user":"tobi"}', done
)
500 it('should work without content-type', function (done
) {
501 var app
= createApp({ type
: accept
})
503 function accept (req
) {
507 var test
= request(app
).post('/')
508 test
.write('user=tobi')
509 test
.expect(200, '{"user":"tobi"}', done
)
512 it('should not invoke without a body', function (done
) {
513 var app
= createApp({ type
: accept
})
515 function accept (req
) {
516 throw new Error('oops!')
526 describe('with verify option', function () {
527 it('should assert value if function', function () {
528 assert
.throws(createApp
.bind(null, { verify
: 'lol' }),
529 /TypeError: option verify must be function/)
532 it('should error from verify', function (done
) {
533 var app
= createApp({
534 verify: function (req
, res
, buf
) {
535 if (buf
[0] === 0x20) throw new Error('no leading space')
541 .set('Content-Type', 'application/x-www-form-urlencoded')
543 .expect(403, '[entity.verify.failed] no leading space', done
)
546 it('should allow custom codes', function (done
) {
547 var app
= createApp({
548 verify: function (req
, res
, buf
) {
549 if (buf
[0] !== 0x20) return
550 var err
= new Error('no leading space')
558 .set('Content-Type', 'application/x-www-form-urlencoded')
560 .expect(400, '[entity.verify.failed] no leading space', done
)
563 it('should allow custom type', function (done
) {
564 var app
= createApp({
565 verify: function (req
, res
, buf
) {
566 if (buf
[0] !== 0x20) return
567 var err
= new Error('no leading space')
575 .set('Content-Type', 'application/x-www-form-urlencoded')
577 .expect(403, '[foo.bar] no leading space', done
)
580 it('should allow pass-through', function (done
) {
581 var app
= createApp({
582 verify: function (req
, res
, buf
) {
583 if (buf
[0] === 0x5b) throw new Error('no arrays')
589 .set('Content-Type', 'application/x-www-form-urlencoded')
591 .expect(200, '{"user":"tobi"}', done
)
594 it('should 415 on unknown charset prior to verify', function (done
) {
595 var app
= createApp({
596 verify: function (req
, res
, buf
) {
597 throw new Error('unexpected verify call')
601 var test
= request(app
).post('/')
602 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=x-bogus')
603 test
.write(Buffer
.from('00000000', 'hex'))
604 test
.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done
)
608 describeAsyncHooks('async local storage', function () {
611 var store
= { foo
: 'bar' }
613 app
.use(function (req
, res
, next
) {
614 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
615 req
.asyncLocalStorage
.run(store
, next
)
618 app
.use(express
.urlencoded())
620 app
.use(function (req
, res
, next
) {
621 var local
= req
.asyncLocalStorage
.getStore()
624 res
.setHeader('x-store-foo', String(local
.foo
))
630 app
.use(function (err
, req
, res
, next
) {
631 var local
= req
.asyncLocalStorage
.getStore()
634 res
.setHeader('x-store-foo', String(local
.foo
))
637 res
.status(err
.status
|| 500)
638 res
.send('[' + err
.type
+ '] ' + err
.message
)
641 app
.post('/', function (req
, res
) {
648 it('should presist store', function (done
) {
651 .set('Content-Type', 'application/x-www-form-urlencoded')
654 .expect('x-store-foo', 'bar')
655 .expect('{"user":"tobi"}')
659 it('should presist store when unmatched content-type', function (done
) {
662 .set('Content-Type', 'application/fizzbuzz')
665 .expect('x-store-foo', 'bar')
669 it('should presist store when inflated', function (done
) {
670 var test
= request(this.app
).post('/')
671 test
.set('Content-Encoding', 'gzip')
672 test
.set('Content-Type', 'application/x-www-form-urlencoded')
673 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
675 test
.expect('x-store-foo', 'bar')
676 test
.expect('{"name":"论"}')
680 it('should presist store when inflate error', function (done
) {
681 var test
= request(this.app
).post('/')
682 test
.set('Content-Encoding', 'gzip')
683 test
.set('Content-Type', 'application/x-www-form-urlencoded')
684 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad6080000', 'hex'))
686 test
.expect('x-store-foo', 'bar')
690 it('should presist store when limit exceeded', function (done
) {
693 .set('Content-Type', 'application/x-www-form-urlencoded')
694 .send('user=' + Buffer
.alloc(1024 * 100, '.').toString())
696 .expect('x-store-foo', 'bar')
701 describe('charset', function () {
703 this.app
= createApp()
706 it('should parse utf-8', function (done
) {
707 var test
= request(this.app
).post('/')
708 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
709 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
710 test
.expect(200, '{"name":"论"}', done
)
713 it('should parse when content-length != char length', function (done
) {
714 var test
= request(this.app
).post('/')
715 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
716 test
.set('Content-Length', '7')
717 test
.write(Buffer
.from('746573743dc3a5', 'hex'))
718 test
.expect(200, '{"test":"å"}', done
)
721 it('should default to utf-8', function (done
) {
722 var test
= request(this.app
).post('/')
723 test
.set('Content-Type', 'application/x-www-form-urlencoded')
724 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
725 test
.expect(200, '{"name":"论"}', done
)
728 it('should fail on unknown charset', function (done
) {
729 var test
= request(this.app
).post('/')
730 test
.set('Content-Type', 'application/x-www-form-urlencoded; charset=koi8-r')
731 test
.write(Buffer
.from('6e616d653dcec5d4', 'hex'))
732 test
.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done
)
736 describe('encoding', function () {
738 this.app
= createApp({ limit
: '10kb' })
741 it('should parse without encoding', function (done
) {
742 var test
= request(this.app
).post('/')
743 test
.set('Content-Type', 'application/x-www-form-urlencoded')
744 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
745 test
.expect(200, '{"name":"论"}', done
)
748 it('should support identity encoding', function (done
) {
749 var test
= request(this.app
).post('/')
750 test
.set('Content-Encoding', 'identity')
751 test
.set('Content-Type', 'application/x-www-form-urlencoded')
752 test
.write(Buffer
.from('6e616d653de8aeba', 'hex'))
753 test
.expect(200, '{"name":"论"}', done
)
756 it('should support gzip encoding', function (done
) {
757 var test
= request(this.app
).post('/')
758 test
.set('Content-Encoding', 'gzip')
759 test
.set('Content-Type', 'application/x-www-form-urlencoded')
760 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
761 test
.expect(200, '{"name":"论"}', done
)
764 it('should support deflate encoding', function (done
) {
765 var test
= request(this.app
).post('/')
766 test
.set('Content-Encoding', 'deflate')
767 test
.set('Content-Type', 'application/x-www-form-urlencoded')
768 test
.write(Buffer
.from('789ccb4bcc4db57db16e17001068042f', 'hex'))
769 test
.expect(200, '{"name":"论"}', done
)
772 it('should be case-insensitive', function (done
) {
773 var test
= request(this.app
).post('/')
774 test
.set('Content-Encoding', 'GZIP')
775 test
.set('Content-Type', 'application/x-www-form-urlencoded')
776 test
.write(Buffer
.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
777 test
.expect(200, '{"name":"论"}', done
)
780 it('should 415 on unknown encoding', function (done
) {
781 var test
= request(this.app
).post('/')
782 test
.set('Content-Encoding', 'nulls')
783 test
.set('Content-Type', 'application/x-www-form-urlencoded')
784 test
.write(Buffer
.from('000000000000', 'hex'))
785 test
.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done
)
790 function createManyParams (count
) {
799 for (var i
= 1; i
< count
; i
++) {
800 var n
= i
.toString(36)
801 str
+= '&' + n
+ '=' + n
807 function createApp (options
) {
810 app
.use(express
.urlencoded(options
))
812 app
.use(function (err
, req
, res
, next
) {
813 res
.status(err
.status
|| 500)
814 res
.send(String(req
.headers
['x-error-property']
815 ? err
[req
.headers
['x-error-property']]
816 : ('[' + err
.type
+ '] ' + err
.message
)))
819 app
.post('/', function (req
, res
) {
826 function expectKeyCount (count
) {
827 return function (res
) {
828 assert
.strictEqual(Object
.keys(JSON
.parse(res
.text
)).length
, count
)
832 function tryRequire (name
) {