1 | var validator = require('validator');
|
2 | var _ = require('lodash');
|
3 | var utils = require('./utils');
|
4 | var check = require('../check/check');
|
5 | var selectFields = require('../check/select-fields');
|
6 | var validationResult = require('../check/validation-result');
|
7 |
|
8 | var validatorChainSymbol = Symbol('express-validator.validatorChain');
|
9 | var sanitizerSymbol = Symbol('express-validator.sanitizer');
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | validator.extend = function(name, fn) {
|
15 | validator[name] = function() {
|
16 | var args = Array.prototype.slice.call(arguments);
|
17 | args[0] = validator.toString(args[0]);
|
18 | return fn.apply(validator, args);
|
19 | };
|
20 | };
|
21 |
|
22 | validator.init = function() {
|
23 | for (var name in validator) {
|
24 | if (typeof validator[name] !== 'function' || name === 'toString' ||
|
25 | name === 'toDate' || name === 'extend' || name === 'init' ||
|
26 | name === 'isServerSide') {
|
27 | continue;
|
28 | }
|
29 | validator.extend(name, validator[name]);
|
30 | }
|
31 | };
|
32 |
|
33 | validator.toString = function(input) {
|
34 | if (typeof input === 'object' && input !== null && input.toString) {
|
35 | input = input.toString();
|
36 | } else if (input === null || typeof input === 'undefined' || (isNaN(input) && !input.length)) {
|
37 | input = '';
|
38 | }
|
39 | return '' + input;
|
40 | };
|
41 |
|
42 | validator.init();
|
43 |
|
44 |
|
45 | var additionalSanitizers = ['trim', 'ltrim', 'rtrim', 'escape', 'unescape', 'stripLow', 'whitelist', 'blacklist', 'normalizeEmail'];
|
46 |
|
47 | var allLocations = ['params', 'query', 'body', 'headers', 'cookies'];
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | var expressValidator = function(options) {
|
58 | options = options || {};
|
59 | var defaults = {
|
60 | customValidators: {},
|
61 | customSanitizers: {},
|
62 | errorFormatter: function(param, msg, value, location) {
|
63 | return {
|
64 | location,
|
65 | param: param,
|
66 | msg: msg,
|
67 | value: value
|
68 | };
|
69 | }
|
70 | };
|
71 |
|
72 | _.defaults(options, defaults);
|
73 |
|
74 | |
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | function Sanitizer(param, req, locations) {
|
84 | this.values = locations.map(function(location) {
|
85 | return _.get(req[location], param);
|
86 | });
|
87 |
|
88 | this.req = req;
|
89 | this.param = param;
|
90 | this.locations = locations;
|
91 |
|
92 | req[sanitizerSymbol].forEach(customSanitizers => {
|
93 | utils.mapAndExtend(customSanitizers, Sanitizer.prototype, utils.makeSanitizer);
|
94 | });
|
95 |
|
96 | return this;
|
97 | }
|
98 |
|
99 | function createValidationChain(field, location, message, req, contexts) {
|
100 | const chain = check([field], Array.isArray(location) ? location : [location], message);
|
101 | contexts.push(chain._context);
|
102 |
|
103 | chain.notEmpty = () => chain.isLength({ min: 1 });
|
104 | chain.len = chain.isLength;
|
105 |
|
106 | req[validatorChainSymbol].forEach(customValidators => {
|
107 | Object.keys(customValidators).forEach(name => {
|
108 | chain[name] = (...options) => {
|
109 | chain._context.validators.push({
|
110 | options,
|
111 | negated: chain._context.negateNext,
|
112 | validator: customValidators[name]
|
113 | });
|
114 | chain._context.negateNext = false;
|
115 | return chain;
|
116 | };
|
117 | });
|
118 | });
|
119 |
|
120 | req[sanitizerSymbol].forEach(customSanitizers => {
|
121 | Object.keys(customSanitizers).forEach(name => {
|
122 | chain[name] = (...options) => {
|
123 | chain._context.sanitizers.push({
|
124 | options,
|
125 | sanitizer: customSanitizers[name]
|
126 | });
|
127 | return chain;
|
128 | };
|
129 | });
|
130 | });
|
131 |
|
132 | return chain;
|
133 | }
|
134 |
|
135 | |
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | function validateSchema(schema, req, loc, contexts) {
|
164 | var currentLoc = loc;
|
165 |
|
166 | for (var param in schema) {
|
167 | const paramCfg = schema[param];
|
168 |
|
169 |
|
170 | if (paramCfg.hasOwnProperty('in')) {
|
171 | if (allLocations.indexOf(paramCfg.in) !== -1) {
|
172 | currentLoc = paramCfg.in;
|
173 | } else {
|
174 |
|
175 | continue;
|
176 | }
|
177 | } else {
|
178 | currentLoc = loc === 'any' ? allLocations : currentLoc;
|
179 | }
|
180 |
|
181 | const paramErrorMessage = paramCfg.errorMessage;
|
182 | var validator = createValidationChain(param, currentLoc, paramErrorMessage, req, contexts);
|
183 |
|
184 | for (var methodName in paramCfg) {
|
185 | const methodCfg = paramCfg[methodName];
|
186 |
|
187 | if (methodName === 'in') {
|
188 | |
189 |
|
190 |
|
191 |
|
192 | currentLoc = loc;
|
193 | continue;
|
194 | }
|
195 |
|
196 | if (methodName === 'errorMessage' || !methodCfg) {
|
197 |
|
198 |
|
199 | continue;
|
200 | }
|
201 |
|
202 | if (typeof validator[methodName] !== 'function') {
|
203 | console.warn('express-validator: a validator with name ' + methodName + ' does not exist');
|
204 | continue;
|
205 | }
|
206 |
|
207 | let opts = methodCfg.options || [];
|
208 | if (opts != null && !Array.isArray(opts)) {
|
209 | opts = [opts];
|
210 | }
|
211 |
|
212 | validator[methodName](...opts);
|
213 | methodName !== 'optional' && validator.withMessage(methodCfg.errorMessage);
|
214 | }
|
215 | }
|
216 | }
|
217 |
|
218 | |
219 |
|
220 |
|
221 |
|
222 | function errorFormatter({ param, msg, value, location }) {
|
223 | return options.errorFormatter(param, msg, value, location);
|
224 | }
|
225 |
|
226 |
|
227 | _.forEach(validator, function(method, methodName) {
|
228 | if (methodName.match(/^to/) || _.includes(additionalSanitizers, methodName)) {
|
229 | Sanitizer.prototype[methodName] = utils.makeSanitizer(methodName, validator);
|
230 | }
|
231 | });
|
232 |
|
233 | utils.mapAndExtend(options.customSanitizers, Sanitizer.prototype, utils.makeSanitizer);
|
234 |
|
235 | return function(req, res, next) {
|
236 | const contexts = [];
|
237 | function runContexts() {
|
238 | contexts.filter(context => !context.promise).forEach(context => {
|
239 | const field = selectFields(req, context)[0];
|
240 | if (!field) {
|
241 | context.promise = Promise.resolve();
|
242 | return;
|
243 | }
|
244 |
|
245 | const promises = context.validators.map(validatorCfg => {
|
246 | const result = validatorCfg.validator(field.value, ...validatorCfg.options);
|
247 | const errorObj = {
|
248 | location: field.location,
|
249 | value: field.value,
|
250 | param: utils.formatParamOutput(field.path),
|
251 | msg: utils.replaceArgs(
|
252 | validatorCfg.message || context.message || 'Invalid value',
|
253 | [field.value, ...validatorCfg.options]
|
254 | )
|
255 | };
|
256 |
|
257 | if (result && result.then) {
|
258 | req._asyncValidationErrors.push(result.then(() => {
|
259 | validatorCfg.negated && req._validationErrors.push(errorObj);
|
260 | }, () => {
|
261 | !validatorCfg.negated && req._validationErrors.push(errorObj);
|
262 | }));
|
263 | } else if ((!validatorCfg.negated && !result) || (validatorCfg.negated && result)) {
|
264 | req._validationErrors.push(errorObj);
|
265 | }
|
266 | });
|
267 |
|
268 | context.promise = Promise.all(promises);
|
269 | });
|
270 | }
|
271 |
|
272 | var locations = ['body', 'params', 'query', 'cookies'];
|
273 |
|
274 |
|
275 | req[validatorChainSymbol] = req[validatorChainSymbol] || [];
|
276 | req[validatorChainSymbol].push(options.customValidators);
|
277 |
|
278 |
|
279 | req[sanitizerSymbol] = req[sanitizerSymbol] || [];
|
280 | req[sanitizerSymbol].push(options.customSanitizers);
|
281 |
|
282 | req._validationErrors = [];
|
283 | req._asyncValidationErrors = [];
|
284 | req.validationErrors = function(mapped) {
|
285 | runContexts();
|
286 |
|
287 | var result = validationResult(req).formatWith(errorFormatter);
|
288 | if (result.isEmpty()) {
|
289 | return false;
|
290 | }
|
291 |
|
292 | return mapped ? result.mapped() : result.array();
|
293 | };
|
294 |
|
295 | req.asyncValidationErrors = function(mapped) {
|
296 | runContexts();
|
297 | return Promise.all(req._asyncValidationErrors).then(() => {
|
298 | if (req._validationErrors.length > 0) {
|
299 | return Promise.reject(req.validationErrors(mapped, true));
|
300 | }
|
301 |
|
302 | return Promise.resolve();
|
303 | });
|
304 | };
|
305 |
|
306 | req.getValidationResult = function() {
|
307 | runContexts();
|
308 | return Promise.all(req._asyncValidationErrors).then(() => {
|
309 | return validationResult(req).formatWith(errorFormatter);
|
310 | });
|
311 | };
|
312 |
|
313 | locations.forEach(function(location) {
|
314 | |
315 |
|
316 |
|
317 |
|
318 |
|
319 | |
320 |
|
321 |
|
322 |
|
323 |
|
324 | |
325 |
|
326 |
|
327 |
|
328 |
|
329 | req['sanitize' + _.capitalize(location)] = function(param) {
|
330 | return new Sanitizer(param, req, [location]);
|
331 | };
|
332 | });
|
333 |
|
334 | req.sanitizeHeaders = function(param) {
|
335 | if (param === 'referrer') {
|
336 | param = 'referer';
|
337 | }
|
338 |
|
339 | return new Sanitizer(param.toLowerCase(), req, ['headers']);
|
340 | };
|
341 |
|
342 | req.sanitize = function(param) {
|
343 | return new Sanitizer(param, req, locations);
|
344 | };
|
345 |
|
346 | locations.forEach(function(location) {
|
347 | |
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | |
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 | |
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | |
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | req['check' + _.capitalize(location)] = function(param, failMsg) {
|
372 | if (_.isPlainObject(param)) {
|
373 | return validateSchema(param, req, location, contexts);
|
374 | }
|
375 | return createValidationChain(param, location, failMsg, req, contexts);
|
376 | };
|
377 | });
|
378 |
|
379 | req.checkHeaders = function(param, failMsg) {
|
380 | if (_.isPlainObject(param)) {
|
381 | return validateSchema(param, req, 'headers', contexts);
|
382 | }
|
383 |
|
384 | if (param === 'referrer') {
|
385 | param = 'referer';
|
386 | }
|
387 |
|
388 | return createValidationChain(param.toLowerCase(), 'headers', failMsg, req, contexts);
|
389 | };
|
390 |
|
391 | req.check = function(param, failMsg) {
|
392 | if (_.isPlainObject(param)) {
|
393 | return validateSchema(param, req, 'any', contexts);
|
394 | }
|
395 | return createValidationChain(param, allLocations, failMsg, req, contexts);
|
396 | };
|
397 |
|
398 | req.filter = req.sanitize;
|
399 | req.assert = req.check;
|
400 | req.validate = req.check;
|
401 |
|
402 | next();
|
403 | };
|
404 | };
|
405 |
|
406 | module.exports = expressValidator;
|
407 | module.exports.validator = validator;
|
408 | module.exports.utils = utils;
|