1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict'
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var bytes = require('bytes')
|
16 | var contentType = require('content-type')
|
17 | var createError = require('http-errors')
|
18 | var debug = require('debug')('body-parser:json')
|
19 | var read = require('../read')
|
20 | var typeis = require('type-is')
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | module.exports = json
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*(.)/
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | function json (options) {
|
51 | var opts = options || {}
|
52 |
|
53 | var limit = typeof opts.limit !== 'number'
|
54 | ? bytes.parse(opts.limit || '100kb')
|
55 | : opts.limit
|
56 | var inflate = opts.inflate !== false
|
57 | var reviver = opts.reviver
|
58 | var strict = opts.strict !== false
|
59 | var type = opts.type || 'application/json'
|
60 | var verify = opts.verify || false
|
61 |
|
62 | if (verify !== false && typeof verify !== 'function') {
|
63 | throw new TypeError('option verify must be function')
|
64 | }
|
65 |
|
66 |
|
67 | var shouldParse = typeof type !== 'function'
|
68 | ? typeChecker(type)
|
69 | : type
|
70 |
|
71 | function parse (body) {
|
72 | if (body.length === 0) {
|
73 |
|
74 |
|
75 | return {}
|
76 | }
|
77 |
|
78 | if (strict) {
|
79 | var first = firstchar(body)
|
80 |
|
81 | if (first !== '{' && first !== '[') {
|
82 | debug('strict violation')
|
83 | throw createStrictSyntaxError(body, first)
|
84 | }
|
85 | }
|
86 |
|
87 | try {
|
88 | debug('parse json')
|
89 | return JSON.parse(body, reviver)
|
90 | } catch (e) {
|
91 | throw normalizeJsonSyntaxError(e, {
|
92 | message: e.message,
|
93 | stack: e.stack
|
94 | })
|
95 | }
|
96 | }
|
97 |
|
98 | return function jsonParser (req, res, next) {
|
99 | if (req._body) {
|
100 | debug('body already parsed')
|
101 | next()
|
102 | return
|
103 | }
|
104 |
|
105 | req.body = req.body || {}
|
106 |
|
107 |
|
108 | if (!typeis.hasBody(req)) {
|
109 | debug('skip empty body')
|
110 | next()
|
111 | return
|
112 | }
|
113 |
|
114 | debug('content-type %j', req.headers['content-type'])
|
115 |
|
116 |
|
117 | if (!shouldParse(req)) {
|
118 | debug('skip parsing')
|
119 | next()
|
120 | return
|
121 | }
|
122 |
|
123 |
|
124 | var charset = getCharset(req) || 'utf-8'
|
125 | if (charset.substr(0, 4) !== 'utf-') {
|
126 | debug('invalid charset')
|
127 | next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
|
128 | charset: charset,
|
129 | type: 'charset.unsupported'
|
130 | }))
|
131 | return
|
132 | }
|
133 |
|
134 |
|
135 | read(req, res, next, parse, debug, {
|
136 | encoding: charset,
|
137 | inflate: inflate,
|
138 | limit: limit,
|
139 | verify: verify
|
140 | })
|
141 | }
|
142 | }
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | function createStrictSyntaxError (str, char) {
|
154 | var index = str.indexOf(char)
|
155 | var partial = str.substring(0, index) + '#'
|
156 |
|
157 | try {
|
158 | JSON.parse(partial); throw new SyntaxError('strict violation')
|
159 | } catch (e) {
|
160 | return normalizeJsonSyntaxError(e, {
|
161 | message: e.message.replace('#', char),
|
162 | stack: e.stack
|
163 | })
|
164 | }
|
165 | }
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | function firstchar (str) {
|
176 | return FIRST_CHAR_REGEXP.exec(str)[1]
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | function getCharset (req) {
|
187 | try {
|
188 | return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
189 | } catch (e) {
|
190 | return undefined
|
191 | }
|
192 | }
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | function normalizeJsonSyntaxError (error, obj) {
|
203 | var keys = Object.getOwnPropertyNames(error)
|
204 |
|
205 | for (var i = 0; i < keys.length; i++) {
|
206 | var key = keys[i]
|
207 | if (key !== 'stack' && key !== 'message') {
|
208 | delete error[key]
|
209 | }
|
210 | }
|
211 |
|
212 |
|
213 | error.stack = obj.stack.replace(error.message, obj.message)
|
214 | error.message = obj.message
|
215 |
|
216 | return error
|
217 | }
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | function typeChecker (type) {
|
227 | return function checkType (req) {
|
228 | return Boolean(typeis(req, type))
|
229 | }
|
230 | }
|