diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-11-27 17:30:46 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-11-27 19:43:01 +0100 |
commit | a2431b7dcbc72c05101dcdbe631ff84a823aeb51 (patch) | |
tree | 09278a822905622a70ff976a75e09d99bc45639a /server/middlewares/validators | |
parent | fcaf1e0aa84213a1b1f1b1a44a3276eae35ebe70 (diff) | |
download | PeerTube-a2431b7dcbc72c05101dcdbe631ff84a823aeb51.tar.gz PeerTube-a2431b7dcbc72c05101dcdbe631ff84a823aeb51.tar.zst PeerTube-a2431b7dcbc72c05101dcdbe631ff84a823aeb51.zip |
Refractor validators
Diffstat (limited to 'server/middlewares/validators')
-rw-r--r-- | server/middlewares/validators/account.ts | 15 | ||||
-rw-r--r-- | server/middlewares/validators/activitypub/activity.ts | 6 | ||||
-rw-r--r-- | server/middlewares/validators/activitypub/signature.ts | 17 | ||||
-rw-r--r-- | server/middlewares/validators/follows.ts | 32 | ||||
-rw-r--r-- | server/middlewares/validators/oembed.ts | 59 | ||||
-rw-r--r-- | server/middlewares/validators/pagination.ts | 9 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 6 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 198 | ||||
-rw-r--r-- | server/middlewares/validators/utils.ts | 14 | ||||
-rw-r--r-- | server/middlewares/validators/video-blacklist.ts | 63 | ||||
-rw-r--r-- | server/middlewares/validators/video-channels.ts | 129 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 252 | ||||
-rw-r--r-- | server/middlewares/validators/webfinger.ts | 42 |
13 files changed, 393 insertions, 449 deletions
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts index 47ed6a7bb..70f4e4d3b 100644 --- a/server/middlewares/validators/account.ts +++ b/server/middlewares/validators/account.ts | |||
@@ -1,18 +1,19 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator/check' | 2 | import { param } from 'express-validator/check' |
3 | import { logger } from '../../helpers' | 3 | import { logger, isLocalAccountNameExist } from '../../helpers' |
4 | import { checkLocalAccountNameExists, isAccountNameValid } from '../../helpers/custom-validators/accounts' | 4 | import { isAccountNameValid } from '../../helpers/custom-validators/accounts' |
5 | import { checkErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | 6 | ||
7 | const localAccountValidator = [ | 7 | const localAccountValidator = [ |
8 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), | 8 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), |
9 | 9 | ||
10 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 10 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
11 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) | 11 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) |
12 | 12 | ||
13 | checkErrors(req, res, () => { | 13 | if (areValidationErrors(req, res)) return |
14 | checkLocalAccountNameExists(req.params.name, res, next) | 14 | if (!await isLocalAccountNameExist(req.params.name, res)) return |
15 | }) | 15 | |
16 | return next() | ||
16 | } | 17 | } |
17 | ] | 18 | ] |
18 | 19 | ||
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts index 0de8b2d85..8aa82298c 100644 --- a/server/middlewares/validators/activitypub/activity.ts +++ b/server/middlewares/validators/activitypub/activity.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isRootActivityValid, logger } from '../../../helpers' | 3 | import { isRootActivityValid, logger } from '../../../helpers' |
4 | import { checkErrors } from '../utils' | 4 | import { areValidationErrors } from '../utils' |
5 | 5 | ||
6 | const activityPubValidator = [ | 6 | const activityPubValidator = [ |
7 | body('').custom((value, { req }) => isRootActivityValid(req.body)), | 7 | body('').custom((value, { req }) => isRootActivityValid(req.body)), |
@@ -9,7 +9,9 @@ const activityPubValidator = [ | |||
9 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 9 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
10 | logger.debug('Checking activity pub parameters', { parameters: req.body }) | 10 | logger.debug('Checking activity pub parameters', { parameters: req.body }) |
11 | 11 | ||
12 | checkErrors(req, res, next) | 12 | if (areValidationErrors(req, res)) return |
13 | |||
14 | return next() | ||
13 | } | 15 | } |
14 | ] | 16 | ] |
15 | 17 | ||
diff --git a/server/middlewares/validators/activitypub/signature.ts b/server/middlewares/validators/activitypub/signature.ts index 0ce15c1f6..360685512 100644 --- a/server/middlewares/validators/activitypub/signature.ts +++ b/server/middlewares/validators/activitypub/signature.ts | |||
@@ -1,14 +1,7 @@ | |||
1 | import { body } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import { body } from 'express-validator/check' | |
4 | import { | 3 | import { isDateValid, isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid, logger } from '../../../helpers' |
5 | logger, | 4 | import { areValidationErrors } from '../utils' |
6 | isDateValid, | ||
7 | isSignatureTypeValid, | ||
8 | isSignatureCreatorValid, | ||
9 | isSignatureValueValid | ||
10 | } from '../../../helpers' | ||
11 | import { checkErrors } from '../utils' | ||
12 | 5 | ||
13 | const signatureValidator = [ | 6 | const signatureValidator = [ |
14 | body('signature.type').custom(isSignatureTypeValid).withMessage('Should have a valid signature type'), | 7 | body('signature.type').custom(isSignatureTypeValid).withMessage('Should have a valid signature type'), |
@@ -19,7 +12,9 @@ const signatureValidator = [ | |||
19 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 12 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
20 | logger.debug('Checking activitypub signature parameter', { parameters: { signature: req.body.signature } }) | 13 | logger.debug('Checking activitypub signature parameter', { parameters: { signature: req.body.signature } }) |
21 | 14 | ||
22 | checkErrors(req, res, next) | 15 | if (areValidationErrors(req, res)) return |
16 | |||
17 | return next() | ||
23 | } | 18 | } |
24 | ] | 19 | ] |
25 | 20 | ||
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index ddc4c1de1..605872ecf 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -4,7 +4,7 @@ import { isTestInstance } from '../../helpers/core-utils' | |||
4 | import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' | 4 | import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { CONFIG, database as db } from '../../initializers' | 6 | import { CONFIG, database as db } from '../../initializers' |
7 | import { checkErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
8 | import { getServerAccount } from '../../helpers/utils' | 8 | import { getServerAccount } from '../../helpers/utils' |
9 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 9 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
10 | 10 | ||
@@ -23,34 +23,30 @@ const followValidator = [ | |||
23 | 23 | ||
24 | logger.debug('Checking follow parameters', { parameters: req.body }) | 24 | logger.debug('Checking follow parameters', { parameters: req.body }) |
25 | 25 | ||
26 | checkErrors(req, res, next) | 26 | if (areValidationErrors(req, res)) return |
27 | |||
28 | return next() | ||
27 | } | 29 | } |
28 | ] | 30 | ] |
29 | 31 | ||
30 | const removeFollowingValidator = [ | 32 | const removeFollowingValidator = [ |
31 | param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), | 33 | param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), |
32 | 34 | ||
33 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 35 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
34 | logger.debug('Checking unfollow parameters', { parameters: req.params }) | 36 | logger.debug('Checking unfollow parameters', { parameters: req.params }) |
35 | 37 | ||
36 | checkErrors(req, res, async () => { | 38 | if (areValidationErrors(req, res)) return |
37 | try { | ||
38 | const serverAccount = await getServerAccount() | ||
39 | const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId) | ||
40 | 39 | ||
41 | if (!follow) { | 40 | const serverAccount = await getServerAccount() |
42 | return res.status(404) | 41 | const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId) |
43 | .end() | ||
44 | } | ||
45 | 42 | ||
46 | res.locals.follow = follow | 43 | if (!follow) { |
44 | return res.status(404) | ||
45 | .end() | ||
46 | } | ||
47 | 47 | ||
48 | return next() | 48 | res.locals.follow = follow |
49 | } catch (err) { | 49 | return next() |
50 | logger.error('Error in remove following validator.', err) | ||
51 | return res.sendStatus(500) | ||
52 | } | ||
53 | }) | ||
54 | } | 50 | } |
55 | ] | 51 | ] |
56 | 52 | ||
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts index f8e34d2d4..31f06dc65 100644 --- a/server/middlewares/validators/oembed.ts +++ b/server/middlewares/validators/oembed.ts | |||
@@ -1,15 +1,10 @@ | |||
1 | import { query } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator/check' | ||
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | 4 | import { isIdOrUUIDValid, isTestInstance, logger } from '../../helpers' | |
5 | import { checkErrors } from './utils' | ||
6 | import { CONFIG } from '../../initializers' | 5 | import { CONFIG } from '../../initializers' |
7 | import { | 6 | import { areValidationErrors } from './utils' |
8 | logger, | 7 | import { isVideoExist } from '../../helpers/custom-validators/videos' |
9 | isTestInstance, | ||
10 | checkVideoExists, | ||
11 | isIdOrUUIDValid | ||
12 | } from '../../helpers' | ||
13 | 8 | ||
14 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' | 9 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' |
15 | const videoWatchRegex = new RegExp('([^/]+)$') | 10 | const videoWatchRegex = new RegExp('([^/]+)$') |
@@ -29,33 +24,35 @@ const oembedValidator = [ | |||
29 | query('maxheight').optional().isInt().withMessage('Should have a valid max height'), | 24 | query('maxheight').optional().isInt().withMessage('Should have a valid max height'), |
30 | query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'), | 25 | query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'), |
31 | 26 | ||
32 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 27 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
33 | logger.debug('Checking oembed parameters', { parameters: req.query }) | 28 | logger.debug('Checking oembed parameters', { parameters: req.query }) |
34 | 29 | ||
35 | checkErrors(req, res, () => { | 30 | if (areValidationErrors(req, res)) return |
36 | if (req.query.format !== undefined && req.query.format !== 'json') { | 31 | |
37 | return res.status(501) | 32 | if (req.query.format !== undefined && req.query.format !== 'json') { |
38 | .json({ error: 'Requested format is not implemented on server.' }) | 33 | return res.status(501) |
39 | .end() | 34 | .json({ error: 'Requested format is not implemented on server.' }) |
40 | } | 35 | .end() |
36 | } | ||
37 | |||
38 | const startIsOk = req.query.url.startsWith(urlShouldStartWith) | ||
39 | const matches = videoWatchRegex.exec(req.query.url) | ||
40 | if (startIsOk === false || matches === null) { | ||
41 | return res.status(400) | ||
42 | .json({ error: 'Invalid url.' }) | ||
43 | .end() | ||
44 | } | ||
41 | 45 | ||
42 | const startIsOk = req.query.url.startsWith(urlShouldStartWith) | 46 | const videoId = matches[1] |
43 | const matches = videoWatchRegex.exec(req.query.url) | 47 | if (isIdOrUUIDValid(videoId) === false) { |
44 | if (startIsOk === false || matches === null) { | 48 | return res.status(400) |
45 | return res.status(400) | 49 | .json({ error: 'Invalid video id.' }) |
46 | .json({ error: 'Invalid url.' }) | 50 | .end() |
47 | .end() | 51 | } |
48 | } | ||
49 | 52 | ||
50 | const videoId = matches[1] | 53 | if (!await isVideoExist(videoId, res)) return |
51 | if (isIdOrUUIDValid(videoId) === false) { | ||
52 | return res.status(400) | ||
53 | .json({ error: 'Invalid video id.' }) | ||
54 | .end() | ||
55 | } | ||
56 | 54 | ||
57 | checkVideoExists(videoId, res, next) | 55 | return next() |
58 | }) | ||
59 | } | 56 | } |
60 | ] | 57 | ] |
61 | 58 | ||
diff --git a/server/middlewares/validators/pagination.ts b/server/middlewares/validators/pagination.ts index a5a542cdf..0895b4eb8 100644 --- a/server/middlewares/validators/pagination.ts +++ b/server/middlewares/validators/pagination.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { query } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import { query } from 'express-validator/check' | |
4 | import { checkErrors } from './utils' | ||
5 | import { logger } from '../../helpers' | 3 | import { logger } from '../../helpers' |
4 | import { areValidationErrors } from './utils' | ||
6 | 5 | ||
7 | const paginationValidator = [ | 6 | const paginationValidator = [ |
8 | query('start').optional().isInt().withMessage('Should have a number start'), | 7 | query('start').optional().isInt().withMessage('Should have a number start'), |
@@ -11,7 +10,9 @@ const paginationValidator = [ | |||
11 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 10 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
12 | logger.debug('Checking pagination parameters', { parameters: req.query }) | 11 | logger.debug('Checking pagination parameters', { parameters: req.query }) |
13 | 12 | ||
14 | checkErrors(req, res, next) | 13 | if (areValidationErrors(req, res)) return |
14 | |||
15 | return next() | ||
15 | } | 16 | } |
16 | ] | 17 | ] |
17 | 18 | ||
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 6fea41bb8..636f68885 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { query } from 'express-validator/check' | 1 | import { query } from 'express-validator/check' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | 3 | ||
4 | import { checkErrors } from './utils' | ||
5 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
6 | import { SORTABLE_COLUMNS } from '../../initializers' | 5 | import { SORTABLE_COLUMNS } from '../../initializers' |
6 | import { areValidationErrors } from './utils' | ||
7 | 7 | ||
8 | // Initialize constants here for better performances | 8 | // Initialize constants here for better performances |
9 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) | 9 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) |
@@ -43,7 +43,9 @@ function checkSort (sortableColumns: string[]) { | |||
43 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 43 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
44 | logger.debug('Checking sort parameters', { parameters: req.query }) | 44 | logger.debug('Checking sort parameters', { parameters: req.query }) |
45 | 45 | ||
46 | checkErrors(req, res, next) | 46 | if (areValidationErrors(req, res)) return |
47 | |||
48 | return next() | ||
47 | } | 49 | } |
48 | ] | 50 | ] |
49 | } | 51 | } |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 6b845f62b..ac7435b7d 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -1,22 +1,19 @@ | |||
1 | import { body, param } from 'express-validator/check' | ||
2 | import 'express-validator' | ||
3 | import * as express from 'express' | 1 | import * as express from 'express' |
4 | import * as Promise from 'bluebird' | 2 | import 'express-validator' |
5 | import * as validator from 'validator' | 3 | import { body, param } from 'express-validator/check' |
6 | |||
7 | import { database as db } from '../../initializers/database' | ||
8 | import { checkErrors } from './utils' | ||
9 | import { | 4 | import { |
5 | isIdOrUUIDValid, | ||
10 | isSignupAllowed, | 6 | isSignupAllowed, |
11 | logger, | 7 | isUserDisplayNSFWValid, |
12 | isUserUsernameValid, | ||
13 | isUserPasswordValid, | 8 | isUserPasswordValid, |
9 | isUserRoleValid, | ||
10 | isUserUsernameValid, | ||
14 | isUserVideoQuotaValid, | 11 | isUserVideoQuotaValid, |
15 | isUserDisplayNSFWValid, | 12 | logger |
16 | isIdOrUUIDValid, | ||
17 | isUserRoleValid | ||
18 | } from '../../helpers' | 13 | } from '../../helpers' |
19 | import { UserInstance, VideoInstance } from '../../models' | 14 | import { isVideoExist } from '../../helpers/custom-validators/videos' |
15 | import { database as db } from '../../initializers/database' | ||
16 | import { areValidationErrors } from './utils' | ||
20 | 17 | ||
21 | const usersAddValidator = [ | 18 | const usersAddValidator = [ |
22 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 19 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -25,12 +22,13 @@ const usersAddValidator = [ | |||
25 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), | 22 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), |
26 | body('role').custom(isUserRoleValid).withMessage('Should have a valid role'), | 23 | body('role').custom(isUserRoleValid).withMessage('Should have a valid role'), |
27 | 24 | ||
28 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 25 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
29 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) | 26 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) |
30 | 27 | ||
31 | checkErrors(req, res, () => { | 28 | if (areValidationErrors(req, res)) return |
32 | checkUserDoesNotAlreadyExist(req.body.username, req.body.email, res, next) | 29 | if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return |
33 | }) | 30 | |
31 | return next() | ||
34 | } | 32 | } |
35 | ] | 33 | ] |
36 | 34 | ||
@@ -39,37 +37,33 @@ const usersRegisterValidator = [ | |||
39 | body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), | 37 | body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), |
40 | body('email').isEmail().withMessage('Should have a valid email'), | 38 | body('email').isEmail().withMessage('Should have a valid email'), |
41 | 39 | ||
42 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 40 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
43 | logger.debug('Checking usersRegister parameters', { parameters: req.body }) | 41 | logger.debug('Checking usersRegister parameters', { parameters: req.body }) |
44 | 42 | ||
45 | checkErrors(req, res, () => { | 43 | if (areValidationErrors(req, res)) return |
46 | checkUserDoesNotAlreadyExist(req.body.username, req.body.email, res, next) | 44 | if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return |
47 | }) | 45 | |
46 | return next() | ||
48 | } | 47 | } |
49 | ] | 48 | ] |
50 | 49 | ||
51 | const usersRemoveValidator = [ | 50 | const usersRemoveValidator = [ |
52 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), | 51 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), |
53 | 52 | ||
54 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 53 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
55 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) | 54 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) |
56 | 55 | ||
57 | checkErrors(req, res, () => { | 56 | if (areValidationErrors(req, res)) return |
58 | checkUserExists(req.params.id, res, (err, user) => { | 57 | if (!await checkUserIdExist(req.params.id, res)) return |
59 | if (err) { | 58 | |
60 | logger.error('Error in usersRemoveValidator.', err) | 59 | const user = res.locals.user |
61 | return res.sendStatus(500) | 60 | if (user.username === 'root') { |
62 | } | 61 | return res.status(400) |
63 | 62 | .send({ error: 'Cannot remove the root user' }) | |
64 | if (user.username === 'root') { | 63 | .end() |
65 | return res.status(400) | 64 | } |
66 | .send({ error: 'Cannot remove the root user' }) | 65 | |
67 | .end() | 66 | return next() |
68 | } | ||
69 | |||
70 | return next() | ||
71 | }) | ||
72 | }) | ||
73 | } | 67 | } |
74 | ] | 68 | ] |
75 | 69 | ||
@@ -79,12 +73,13 @@ const usersUpdateValidator = [ | |||
79 | body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), | 73 | body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), |
80 | body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'), | 74 | body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'), |
81 | 75 | ||
82 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 76 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
83 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) | 77 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) |
84 | 78 | ||
85 | checkErrors(req, res, () => { | 79 | if (areValidationErrors(req, res)) return |
86 | checkUserExists(req.params.id, res, next) | 80 | if (!await checkUserIdExist(req.params.id, res)) return |
87 | }) | 81 | |
82 | return next() | ||
88 | } | 83 | } |
89 | ] | 84 | ] |
90 | 85 | ||
@@ -97,64 +92,48 @@ const usersUpdateMeValidator = [ | |||
97 | // TODO: Add old password verification | 92 | // TODO: Add old password verification |
98 | logger.debug('Checking usersUpdateMe parameters', { parameters: req.body }) | 93 | logger.debug('Checking usersUpdateMe parameters', { parameters: req.body }) |
99 | 94 | ||
100 | checkErrors(req, res, next) | 95 | if (areValidationErrors(req, res)) return |
96 | |||
97 | return next() | ||
101 | } | 98 | } |
102 | ] | 99 | ] |
103 | 100 | ||
104 | const usersGetValidator = [ | 101 | const usersGetValidator = [ |
105 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), | 102 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), |
106 | 103 | ||
107 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 104 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
108 | checkErrors(req, res, () => { | 105 | logger.debug('Checking usersGet parameters', { parameters: req.body }) |
109 | checkUserExists(req.params.id, res, next) | 106 | |
110 | }) | 107 | if (areValidationErrors(req, res)) return |
108 | if (!await checkUserIdExist(req.params.id, res)) return | ||
109 | |||
110 | return next() | ||
111 | } | 111 | } |
112 | ] | 112 | ] |
113 | 113 | ||
114 | const usersVideoRatingValidator = [ | 114 | const usersVideoRatingValidator = [ |
115 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 115 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), |
116 | 116 | ||
117 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 117 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
118 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) | 118 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) |
119 | 119 | ||
120 | checkErrors(req, res, () => { | 120 | if (areValidationErrors(req, res)) return |
121 | let videoPromise: Promise<VideoInstance> | 121 | if (!await isVideoExist(req.params.videoId, res)) return |
122 | 122 | ||
123 | if (validator.isUUID(req.params.videoId)) { | 123 | return next() |
124 | videoPromise = db.Video.loadByUUID(req.params.videoId) | ||
125 | } else { | ||
126 | videoPromise = db.Video.load(req.params.videoId) | ||
127 | } | ||
128 | |||
129 | videoPromise | ||
130 | .then(video => { | ||
131 | if (!video) { | ||
132 | return res.status(404) | ||
133 | .json({ error: 'Video not found' }) | ||
134 | .end() | ||
135 | } | ||
136 | |||
137 | return next() | ||
138 | }) | ||
139 | .catch(err => { | ||
140 | logger.error('Error in user request validator.', err) | ||
141 | return res.sendStatus(500) | ||
142 | }) | ||
143 | }) | ||
144 | } | 124 | } |
145 | ] | 125 | ] |
146 | 126 | ||
147 | const ensureUserRegistrationAllowed = [ | 127 | const ensureUserRegistrationAllowed = [ |
148 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 128 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
149 | isSignupAllowed().then(allowed => { | 129 | const allowed = await isSignupAllowed() |
150 | if (allowed === false) { | 130 | if (allowed === false) { |
151 | return res.status(403) | 131 | return res.status(403) |
152 | .send({ error: 'User registration is not enabled or user limit is reached.' }) | 132 | .send({ error: 'User registration is not enabled or user limit is reached.' }) |
153 | .end() | 133 | .end() |
154 | } | 134 | } |
155 | 135 | ||
156 | return next() | 136 | return next() |
157 | }) | ||
158 | } | 137 | } |
159 | ] | 138 | ] |
160 | 139 | ||
@@ -173,37 +152,30 @@ export { | |||
173 | 152 | ||
174 | // --------------------------------------------------------------------------- | 153 | // --------------------------------------------------------------------------- |
175 | 154 | ||
176 | function checkUserExists (id: number, res: express.Response, callback: (err: Error, user: UserInstance) => void) { | 155 | async function checkUserIdExist (id: number, res: express.Response) { |
177 | db.User.loadById(id) | 156 | const user = await db.User.loadById(id) |
178 | .then(user => { | 157 | |
179 | if (!user) { | 158 | if (!user) { |
180 | return res.status(404) | 159 | res.status(404) |
181 | .send({ error: 'User not found' }) | 160 | .send({ error: 'User not found' }) |
182 | .end() | 161 | .end() |
183 | } | 162 | |
184 | 163 | return false | |
185 | res.locals.user = user | 164 | } |
186 | return callback(null, user) | 165 | |
187 | }) | 166 | res.locals.user = user |
188 | .catch(err => { | 167 | return true |
189 | logger.error('Error in user request validator.', err) | ||
190 | return res.sendStatus(500) | ||
191 | }) | ||
192 | } | 168 | } |
193 | 169 | ||
194 | function checkUserDoesNotAlreadyExist (username: string, email: string, res: express.Response, callback: () => void) { | 170 | async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { |
195 | db.User.loadByUsernameOrEmail(username, email) | 171 | const user = await db.User.loadByUsernameOrEmail(username, email) |
196 | .then(user => { | 172 | |
197 | if (user) { | 173 | if (user) { |
198 | return res.status(409) | 174 | res.status(409) |
199 | .send({ error: 'User with this username of email already exists.' }) | 175 | .send({ error: 'User with this username of email already exists.' }) |
200 | .end() | 176 | .end() |
201 | } | 177 | return false |
202 | 178 | } | |
203 | return callback() | 179 | |
204 | }) | 180 | return true |
205 | .catch(err => { | ||
206 | logger.error('Error in usersAdd request validator.', err) | ||
207 | return res.sendStatus(500) | ||
208 | }) | ||
209 | } | 181 | } |
diff --git a/server/middlewares/validators/utils.ts b/server/middlewares/validators/utils.ts index 77a1a0d4b..ca80acf29 100644 --- a/server/middlewares/validators/utils.ts +++ b/server/middlewares/validators/utils.ts | |||
@@ -1,19 +1,8 @@ | |||
1 | import { validationResult } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { validationResult } from 'express-validator/check' | ||
3 | 3 | ||
4 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
5 | 5 | ||
6 | function checkErrors (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
7 | const errors = validationResult(req) | ||
8 | |||
9 | if (!errors.isEmpty()) { | ||
10 | logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) | ||
11 | return res.status(400).json({ errors: errors.mapped() }) | ||
12 | } | ||
13 | |||
14 | return next() | ||
15 | } | ||
16 | |||
17 | function areValidationErrors (req: express.Request, res: express.Response) { | 6 | function areValidationErrors (req: express.Request, res: express.Response) { |
18 | const errors = validationResult(req) | 7 | const errors = validationResult(req) |
19 | 8 | ||
@@ -30,6 +19,5 @@ function areValidationErrors (req: express.Request, res: express.Response) { | |||
30 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
31 | 20 | ||
32 | export { | 21 | export { |
33 | checkErrors, | ||
34 | areValidationErrors | 22 | areValidationErrors |
35 | } | 23 | } |
diff --git a/server/middlewares/validators/video-blacklist.ts b/server/middlewares/validators/video-blacklist.ts index 3c8c31519..f1cc04950 100644 --- a/server/middlewares/validators/video-blacklist.ts +++ b/server/middlewares/validators/video-blacklist.ts | |||
@@ -1,35 +1,36 @@ | |||
1 | import { param } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import { param } from 'express-validator/check' | |
3 | import { isIdOrUUIDValid, logger } from '../../helpers' | ||
4 | import { isVideoExist } from '../../helpers/custom-validators/videos' | ||
4 | import { database as db } from '../../initializers/database' | 5 | import { database as db } from '../../initializers/database' |
5 | import { checkErrors } from './utils' | 6 | import { VideoInstance } from '../../models/video/video-interface' |
6 | import { logger, isIdOrUUIDValid, checkVideoExists } from '../../helpers' | 7 | import { areValidationErrors } from './utils' |
7 | 8 | ||
8 | const videosBlacklistRemoveValidator = [ | 9 | const videosBlacklistRemoveValidator = [ |
9 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 10 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
10 | 11 | ||
11 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 12 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
12 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) | 13 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) |
13 | 14 | ||
14 | checkErrors(req, res, () => { | 15 | if (areValidationErrors(req, res)) return |
15 | checkVideoExists(req.params.videoId, res, () => { | 16 | if (!await isVideoExist(req.params.videoId, res)) return |
16 | checkVideoIsBlacklisted(req, res, next) | 17 | if (!await checkVideoIsBlacklisted(res.locals.video, res)) return |
17 | }) | 18 | |
18 | }) | 19 | return next() |
19 | } | 20 | } |
20 | ] | 21 | ] |
21 | 22 | ||
22 | const videosBlacklistAddValidator = [ | 23 | const videosBlacklistAddValidator = [ |
23 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 24 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
24 | 25 | ||
25 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 26 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
26 | logger.debug('Checking videosBlacklist parameters', { parameters: req.params }) | 27 | logger.debug('Checking videosBlacklist parameters', { parameters: req.params }) |
27 | 28 | ||
28 | checkErrors(req, res, () => { | 29 | if (areValidationErrors(req, res)) return |
29 | checkVideoExists(req.params.videoId, res, () => { | 30 | if (!await isVideoExist(req.params.videoId, res)) return |
30 | checkVideoIsBlacklistable(req, res, next) | 31 | if (!checkVideoIsBlacklistable(res.locals.video, res)) return |
31 | }) | 32 | |
32 | }) | 33 | return next() |
33 | } | 34 | } |
34 | ] | 35 | ] |
35 | 36 | ||
@@ -41,27 +42,27 @@ export { | |||
41 | } | 42 | } |
42 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
43 | 44 | ||
44 | function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) { | 45 | function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) { |
45 | if (res.locals.video.isOwned() === true) { | 46 | if (video.isOwned() === true) { |
46 | return res.status(403) | 47 | res.status(403) |
47 | .json({ error: 'Cannot blacklist a local video' }) | 48 | .json({ error: 'Cannot blacklist a local video' }) |
48 | .end() | 49 | .end() |
50 | |||
51 | return false | ||
49 | } | 52 | } |
50 | 53 | ||
51 | callback() | 54 | return true |
52 | } | 55 | } |
53 | 56 | ||
54 | function checkVideoIsBlacklisted (req: express.Request, res: express.Response, callback: () => void) { | 57 | async function checkVideoIsBlacklisted (video: VideoInstance, res: express.Response) { |
55 | db.BlacklistedVideo.loadByVideoId(res.locals.video.id) | 58 | const blacklistedVideo = await db.BlacklistedVideo.loadByVideoId(video.id) |
56 | .then(blacklistedVideo => { | 59 | if (!blacklistedVideo) { |
57 | if (!blacklistedVideo) return res.status(404).send('Blacklisted video not found') | 60 | res.status(404) |
61 | .send('Blacklisted video not found') | ||
58 | 62 | ||
59 | res.locals.blacklistedVideo = blacklistedVideo | 63 | return false |
64 | } | ||
60 | 65 | ||
61 | callback() | 66 | res.locals.blacklistedVideo = blacklistedVideo |
62 | }) | 67 | return true |
63 | .catch(err => { | ||
64 | logger.error('Error in blacklistRemove request validator', { error: err }) | ||
65 | return res.sendStatus(500) | ||
66 | }) | ||
67 | } | 68 | } |
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts index f30fbf0dc..4683c91e1 100644 --- a/server/middlewares/validators/video-channels.ts +++ b/server/middlewares/validators/video-channels.ts | |||
@@ -1,29 +1,30 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param } from 'express-validator/check' |
3 | import { UserRight } from '../../../shared' | 3 | import { UserRight } from '../../../shared' |
4 | import { checkAccountIdExists } from '../../helpers/custom-validators/accounts' | ||
5 | import { isIdValid } from '../../helpers/custom-validators/misc' | 4 | import { isIdValid } from '../../helpers/custom-validators/misc' |
6 | import { | 5 | import { |
7 | checkVideoChannelExists, | ||
8 | isVideoChannelDescriptionValid, | 6 | isVideoChannelDescriptionValid, |
9 | isVideoChannelExistsPromise, | 7 | isVideoChannelExist, |
10 | isVideoChannelNameValid | 8 | isVideoChannelNameValid |
11 | } from '../../helpers/custom-validators/video-channels' | 9 | } from '../../helpers/custom-validators/video-channels' |
12 | import { isIdOrUUIDValid } from '../../helpers/index' | 10 | import { isIdOrUUIDValid } from '../../helpers/index' |
13 | import { logger } from '../../helpers/logger' | 11 | import { logger } from '../../helpers/logger' |
14 | import { database as db } from '../../initializers' | 12 | import { database as db } from '../../initializers' |
15 | import { UserInstance } from '../../models' | 13 | import { UserInstance } from '../../models' |
16 | import { areValidationErrors, checkErrors } from './utils' | 14 | import { areValidationErrors } from './utils' |
15 | import { isAccountIdExist } from '../../helpers/custom-validators/accounts' | ||
16 | import { VideoChannelInstance } from '../../models/video/video-channel-interface' | ||
17 | 17 | ||
18 | const listVideoAccountChannelsValidator = [ | 18 | const listVideoAccountChannelsValidator = [ |
19 | param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), | 19 | param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), |
20 | 20 | ||
21 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 21 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
22 | logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) | 22 | logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) |
23 | 23 | ||
24 | checkErrors(req, res, () => { | 24 | if (areValidationErrors(req, res)) return |
25 | checkAccountIdExists(req.params.accountId, res, next) | 25 | if (!await isAccountIdExist(req.params.accountId, res)) return |
26 | }) | 26 | |
27 | return next() | ||
27 | } | 28 | } |
28 | ] | 29 | ] |
29 | 30 | ||
@@ -34,7 +35,9 @@ const videoChannelsAddValidator = [ | |||
34 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 35 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
35 | logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body }) | 36 | logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body }) |
36 | 37 | ||
37 | checkErrors(req, res, next) | 38 | if (areValidationErrors(req, res)) return |
39 | |||
40 | return next() | ||
38 | } | 41 | } |
39 | ] | 42 | ] |
40 | 43 | ||
@@ -43,56 +46,56 @@ const videoChannelsUpdateValidator = [ | |||
43 | body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'), | 46 | body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'), |
44 | body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), | 47 | body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), |
45 | 48 | ||
46 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 49 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
47 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) | 50 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) |
48 | 51 | ||
49 | checkErrors(req, res, () => { | 52 | if (areValidationErrors(req, res)) return |
50 | checkVideoChannelExists(req.params.id, res, () => { | 53 | if (!await isVideoChannelExist(req.params.id, res)) return |
51 | // We need to make additional checks | 54 | |
52 | if (res.locals.videoChannel.isOwned() === false) { | 55 | // We need to make additional checks |
53 | return res.status(403) | 56 | if (res.locals.videoChannel.isOwned() === false) { |
54 | .json({ error: 'Cannot update video channel of another server' }) | 57 | return res.status(403) |
55 | .end() | 58 | .json({ error: 'Cannot update video channel of another server' }) |
56 | } | 59 | .end() |
57 | 60 | } | |
58 | if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { | 61 | |
59 | return res.status(403) | 62 | if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { |
60 | .json({ error: 'Cannot update video channel of another user' }) | 63 | return res.status(403) |
61 | .end() | 64 | .json({ error: 'Cannot update video channel of another user' }) |
62 | } | 65 | .end() |
63 | 66 | } | |
64 | next() | 67 | |
65 | }) | 68 | return next() |
66 | }) | ||
67 | } | 69 | } |
68 | ] | 70 | ] |
69 | 71 | ||
70 | const videoChannelsRemoveValidator = [ | 72 | const videoChannelsRemoveValidator = [ |
71 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 73 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
72 | 74 | ||
73 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 75 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
74 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) | 76 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) |
75 | 77 | ||
76 | checkErrors(req, res, () => { | 78 | if (areValidationErrors(req, res)) return |
77 | checkVideoChannelExists(req.params.id, res, () => { | 79 | if (!await isVideoChannelExist(req.params.id, res)) return |
78 | // Check if the user who did the request is able to delete the video | 80 | |
79 | checkUserCanDeleteVideoChannel(res, () => { | 81 | // Check if the user who did the request is able to delete the video |
80 | checkVideoChannelIsNotTheLastOne(res, next) | 82 | if (!checkUserCanDeleteVideoChannel(res.locals.user, res.locals.videoChannel, res)) return |
81 | }) | 83 | if (!await checkVideoChannelIsNotTheLastOne(res)) return |
82 | }) | 84 | |
83 | }) | 85 | return next() |
84 | } | 86 | } |
85 | ] | 87 | ] |
86 | 88 | ||
87 | const videoChannelsGetValidator = [ | 89 | const videoChannelsGetValidator = [ |
88 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 90 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
89 | 91 | ||
90 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 92 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
91 | logger.debug('Checking videoChannelsGet parameters', { parameters: req.params }) | 93 | logger.debug('Checking videoChannelsGet parameters', { parameters: req.params }) |
92 | 94 | ||
93 | checkErrors(req, res, () => { | 95 | if (areValidationErrors(req, res)) return |
94 | checkVideoChannelExists(req.params.id, res, next) | 96 | if (!await isVideoChannelExist(req.params.id, res)) return |
95 | }) | 97 | |
98 | return next() | ||
96 | } | 99 | } |
97 | ] | 100 | ] |
98 | 101 | ||
@@ -104,7 +107,7 @@ const videoChannelsShareValidator = [ | |||
104 | logger.debug('Checking videoChannelShare parameters', { parameters: req.params }) | 107 | logger.debug('Checking videoChannelShare parameters', { parameters: req.params }) |
105 | 108 | ||
106 | if (areValidationErrors(req, res)) return | 109 | if (areValidationErrors(req, res)) return |
107 | if (!await isVideoChannelExistsPromise(req.params.id, res)) return | 110 | if (!await isVideoChannelExist(req.params.id, res)) return |
108 | 111 | ||
109 | const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId) | 112 | const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId) |
110 | if (!share) { | 113 | if (!share) { |
@@ -131,38 +134,40 @@ export { | |||
131 | 134 | ||
132 | // --------------------------------------------------------------------------- | 135 | // --------------------------------------------------------------------------- |
133 | 136 | ||
134 | function checkUserCanDeleteVideoChannel (res: express.Response, callback: () => void) { | 137 | function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: VideoChannelInstance, res: express.Response) { |
135 | const user: UserInstance = res.locals.oauth.token.User | ||
136 | |||
137 | // Retrieve the user who did the request | 138 | // Retrieve the user who did the request |
138 | if (res.locals.videoChannel.isOwned() === false) { | 139 | if (videoChannel.isOwned() === false) { |
139 | return res.status(403) | 140 | res.status(403) |
140 | .json({ error: 'Cannot remove video channel of another server.' }) | 141 | .json({ error: 'Cannot remove video channel of another server.' }) |
141 | .end() | 142 | .end() |
143 | |||
144 | return false | ||
142 | } | 145 | } |
143 | 146 | ||
144 | // Check if the user can delete the video channel | 147 | // Check if the user can delete the video channel |
145 | // The user can delete it if s/he is an admin | 148 | // The user can delete it if s/he is an admin |
146 | // Or if s/he is the video channel's account | 149 | // Or if s/he is the video channel's account |
147 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && res.locals.videoChannel.Account.userId !== user.id) { | 150 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { |
148 | return res.status(403) | 151 | res.status(403) |
149 | .json({ error: 'Cannot remove video channel of another user' }) | 152 | .json({ error: 'Cannot remove video channel of another user' }) |
150 | .end() | 153 | .end() |
154 | |||
155 | return false | ||
151 | } | 156 | } |
152 | 157 | ||
153 | // If we reach this comment, we can delete the video | 158 | return true |
154 | callback() | ||
155 | } | 159 | } |
156 | 160 | ||
157 | function checkVideoChannelIsNotTheLastOne (res: express.Response, callback: () => void) { | 161 | async function checkVideoChannelIsNotTheLastOne (res: express.Response) { |
158 | db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id) | 162 | const count = await db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id) |
159 | .then(count => { | 163 | |
160 | if (count <= 1) { | 164 | if (count <= 1) { |
161 | return res.status(409) | 165 | res.status(409) |
162 | .json({ error: 'Cannot remove the last channel of this user' }) | 166 | .json({ error: 'Cannot remove the last channel of this user' }) |
163 | .end() | 167 | .end() |
164 | } | 168 | |
165 | 169 | return false | |
166 | callback() | 170 | } |
167 | }) | 171 | |
172 | return true | ||
168 | } | 173 | } |
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 5ffc85210..3cbf98312 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -3,11 +3,11 @@ import { body, param, query } from 'express-validator/check' | |||
3 | import { UserRight, VideoPrivacy } from '../../../shared' | 3 | import { UserRight, VideoPrivacy } from '../../../shared' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' |
5 | import { | 5 | import { |
6 | checkVideoExists, | ||
7 | isVideoAbuseReasonValid, | 6 | isVideoAbuseReasonValid, |
8 | isVideoCategoryValid, | 7 | isVideoCategoryValid, |
9 | isVideoDescriptionValid, | 8 | isVideoDescriptionValid, |
10 | isVideoDurationValid, | 9 | isVideoDurationValid, |
10 | isVideoExist, | ||
11 | isVideoFile, | 11 | isVideoFile, |
12 | isVideoLanguageValid, | 12 | isVideoLanguageValid, |
13 | isVideoLicenceValid, | 13 | isVideoLicenceValid, |
@@ -20,12 +20,11 @@ import { | |||
20 | import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' | 20 | import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' |
21 | import { logger } from '../../helpers/logger' | 21 | import { logger } from '../../helpers/logger' |
22 | import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers' | 22 | import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers' |
23 | |||
24 | import { database as db } from '../../initializers/database' | 23 | import { database as db } from '../../initializers/database' |
25 | import { UserInstance } from '../../models/account/user-interface' | 24 | import { UserInstance } from '../../models/account/user-interface' |
25 | import { VideoInstance } from '../../models/video/video-interface' | ||
26 | import { authenticate } from '../oauth' | 26 | import { authenticate } from '../oauth' |
27 | import { areValidationErrors, checkErrors } from './utils' | 27 | import { areValidationErrors } from './utils' |
28 | import { isVideoExistsPromise } from '../../helpers/index' | ||
29 | 28 | ||
30 | const videosAddValidator = [ | 29 | const videosAddValidator = [ |
31 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( | 30 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( |
@@ -42,68 +41,58 @@ const videosAddValidator = [ | |||
42 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | 41 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), |
43 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 42 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
44 | 43 | ||
45 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 44 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
46 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) | 45 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) |
47 | 46 | ||
48 | checkErrors(req, res, () => { | 47 | if (areValidationErrors(req, res)) return |
49 | const videoFile: Express.Multer.File = req.files['videofile'][0] | 48 | |
50 | const user = res.locals.oauth.token.User | 49 | const videoFile: Express.Multer.File = req.files['videofile'][0] |
50 | const user = res.locals.oauth.token.User | ||
51 | 51 | ||
52 | return db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id) | 52 | const videoChannel = await db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id) |
53 | .then(videoChannel => { | 53 | if (!videoChannel) { |
54 | if (!videoChannel) { | 54 | res.status(400) |
55 | res.status(400) | 55 | .json({ error: 'Unknown video video channel for this account.' }) |
56 | .json({ error: 'Unknown video video channel for this account.' }) | 56 | .end() |
57 | .end() | ||
58 | 57 | ||
59 | return undefined | 58 | return |
60 | } | 59 | } |
61 | 60 | ||
62 | res.locals.videoChannel = videoChannel | 61 | res.locals.videoChannel = videoChannel |
63 | 62 | ||
64 | return user.isAbleToUploadVideo(videoFile) | 63 | const isAble = await user.isAbleToUploadVideo(videoFile) |
65 | }) | 64 | if (isAble === false) { |
66 | .then(isAble => { | 65 | res.status(403) |
67 | if (isAble === false) { | 66 | .json({ error: 'The user video quota is exceeded with this video.' }) |
68 | res.status(403) | 67 | .end() |
69 | .json({ error: 'The user video quota is exceeded with this video.' }) | 68 | |
70 | .end() | 69 | return |
71 | 70 | } | |
72 | return undefined | 71 | |
73 | } | 72 | let duration: number |
74 | 73 | ||
75 | return getDurationFromVideoFile(videoFile.path) | 74 | try { |
76 | .catch(err => { | 75 | duration = await getDurationFromVideoFile(videoFile.path) |
77 | logger.error('Invalid input file in videosAddValidator.', err) | 76 | } catch (err) { |
78 | res.status(400) | 77 | logger.error('Invalid input file in videosAddValidator.', err) |
79 | .json({ error: 'Invalid input file.' }) | 78 | res.status(400) |
80 | .end() | 79 | .json({ error: 'Invalid input file.' }) |
81 | 80 | .end() | |
82 | return undefined | 81 | |
83 | }) | 82 | return |
84 | }) | 83 | } |
85 | .then(duration => { | 84 | |
86 | // Previous test failed, abort | 85 | if (!isVideoDurationValid('' + duration)) { |
87 | if (duration === undefined) return undefined | 86 | return res.status(400) |
88 | 87 | .json({ | |
89 | if (!isVideoDurationValid('' + duration)) { | 88 | error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).' |
90 | return res.status(400) | 89 | }) |
91 | .json({ | 90 | .end() |
92 | error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).' | 91 | } |
93 | }) | 92 | |
94 | .end() | 93 | videoFile['duration'] = duration |
95 | } | 94 | |
96 | 95 | return next() | |
97 | videoFile['duration'] = duration | ||
98 | next() | ||
99 | }) | ||
100 | .catch(err => { | ||
101 | logger.error('Error in video add validator', err) | ||
102 | res.sendStatus(500) | ||
103 | |||
104 | return undefined | ||
105 | }) | ||
106 | }) | ||
107 | } | 96 | } |
108 | ] | 97 | ] |
109 | 98 | ||
@@ -118,61 +107,59 @@ const videosUpdateValidator = [ | |||
118 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 107 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
119 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 108 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
120 | 109 | ||
121 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 110 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
122 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) | 111 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) |
123 | 112 | ||
124 | checkErrors(req, res, () => { | 113 | if (areValidationErrors(req, res)) return |
125 | checkVideoExists(req.params.id, res, () => { | 114 | if (!await isVideoExist(req.params.id, res)) return |
126 | const video = res.locals.video | 115 | |
127 | 116 | const video = res.locals.video | |
128 | // We need to make additional checks | 117 | |
129 | if (video.isOwned() === false) { | 118 | // We need to make additional checks |
130 | return res.status(403) | 119 | if (video.isOwned() === false) { |
131 | .json({ error: 'Cannot update video of another server' }) | 120 | return res.status(403) |
132 | .end() | 121 | .json({ error: 'Cannot update video of another server' }) |
133 | } | 122 | .end() |
134 | 123 | } | |
135 | if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) { | 124 | |
136 | return res.status(403) | 125 | if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) { |
137 | .json({ error: 'Cannot update video of another user' }) | 126 | return res.status(403) |
138 | .end() | 127 | .json({ error: 'Cannot update video of another user' }) |
139 | } | 128 | .end() |
140 | 129 | } | |
141 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { | 130 | |
142 | return res.status(409) | 131 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { |
143 | .json({ error: 'Cannot set "private" a video that was not private anymore.' }) | 132 | return res.status(409) |
144 | .end() | 133 | .json({ error: 'Cannot set "private" a video that was not private anymore.' }) |
145 | } | 134 | .end() |
146 | 135 | } | |
147 | next() | 136 | |
148 | }) | 137 | return next() |
149 | }) | ||
150 | } | 138 | } |
151 | ] | 139 | ] |
152 | 140 | ||
153 | const videosGetValidator = [ | 141 | const videosGetValidator = [ |
154 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 142 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
155 | 143 | ||
156 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 144 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
157 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | 145 | logger.debug('Checking videosGet parameters', { parameters: req.params }) |
158 | 146 | ||
159 | checkErrors(req, res, () => { | 147 | if (areValidationErrors(req, res)) return |
160 | checkVideoExists(req.params.id, res, () => { | 148 | if (!await isVideoExist(req.params.id, res)) return |
161 | const video = res.locals.video | ||
162 | 149 | ||
163 | // Video is not private, anyone can access it | 150 | const video = res.locals.video |
164 | if (video.privacy !== VideoPrivacy.PRIVATE) return next() | ||
165 | 151 | ||
166 | authenticate(req, res, () => { | 152 | // Video is not private, anyone can access it |
167 | if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) { | 153 | if (video.privacy !== VideoPrivacy.PRIVATE) return next() |
168 | return res.status(403) | ||
169 | .json({ error: 'Cannot get this private video of another user' }) | ||
170 | .end() | ||
171 | } | ||
172 | 154 | ||
173 | next() | 155 | authenticate(req, res, () => { |
174 | }) | 156 | if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) { |
175 | }) | 157 | return res.status(403) |
158 | .json({ error: 'Cannot get this private video of another user' }) | ||
159 | .end() | ||
160 | } | ||
161 | |||
162 | return next() | ||
176 | }) | 163 | }) |
177 | } | 164 | } |
178 | ] | 165 | ] |
@@ -180,17 +167,16 @@ const videosGetValidator = [ | |||
180 | const videosRemoveValidator = [ | 167 | const videosRemoveValidator = [ |
181 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 168 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
182 | 169 | ||
183 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 170 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
184 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) | 171 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) |
185 | 172 | ||
186 | checkErrors(req, res, () => { | 173 | if (areValidationErrors(req, res)) return |
187 | checkVideoExists(req.params.id, res, () => { | 174 | if (!await isVideoExist(req.params.id, res)) return |
188 | // Check if the user who did the request is able to delete the video | 175 | |
189 | checkUserCanDeleteVideo(res.locals.oauth.token.User, res, () => { | 176 | // Check if the user who did the request is able to delete the video |
190 | next() | 177 | if (!checkUserCanDeleteVideo(res.locals.oauth.token.User, res.locals.video, res)) return |
191 | }) | 178 | |
192 | }) | 179 | return next() |
193 | }) | ||
194 | } | 180 | } |
195 | ] | 181 | ] |
196 | 182 | ||
@@ -201,7 +187,9 @@ const videosSearchValidator = [ | |||
201 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 187 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
202 | logger.debug('Checking videosSearch parameters', { parameters: req.params }) | 188 | logger.debug('Checking videosSearch parameters', { parameters: req.params }) |
203 | 189 | ||
204 | checkErrors(req, res, next) | 190 | if (areValidationErrors(req, res)) return |
191 | |||
192 | return next() | ||
205 | } | 193 | } |
206 | ] | 194 | ] |
207 | 195 | ||
@@ -209,12 +197,13 @@ const videoAbuseReportValidator = [ | |||
209 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 197 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
210 | body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), | 198 | body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), |
211 | 199 | ||
212 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 200 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
213 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | 201 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) |
214 | 202 | ||
215 | checkErrors(req, res, () => { | 203 | if (areValidationErrors(req, res)) return |
216 | checkVideoExists(req.params.id, res, next) | 204 | if (!await isVideoExist(req.params.id, res)) return |
217 | }) | 205 | |
206 | return next() | ||
218 | } | 207 | } |
219 | ] | 208 | ] |
220 | 209 | ||
@@ -222,12 +211,13 @@ const videoRateValidator = [ | |||
222 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 211 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
223 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), | 212 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), |
224 | 213 | ||
225 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 214 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
226 | logger.debug('Checking videoRate parameters', { parameters: req.body }) | 215 | logger.debug('Checking videoRate parameters', { parameters: req.body }) |
227 | 216 | ||
228 | checkErrors(req, res, () => { | 217 | if (areValidationErrors(req, res)) return |
229 | checkVideoExists(req.params.id, res, next) | 218 | if (!await isVideoExist(req.params.id, res)) return |
230 | }) | 219 | |
220 | return next() | ||
231 | } | 221 | } |
232 | ] | 222 | ] |
233 | 223 | ||
@@ -239,7 +229,7 @@ const videosShareValidator = [ | |||
239 | logger.debug('Checking videoShare parameters', { parameters: req.params }) | 229 | logger.debug('Checking videoShare parameters', { parameters: req.params }) |
240 | 230 | ||
241 | if (areValidationErrors(req, res)) return | 231 | if (areValidationErrors(req, res)) return |
242 | if (!await isVideoExistsPromise(req.params.id, res)) return | 232 | if (!await isVideoExist(req.params.id, res)) return |
243 | 233 | ||
244 | const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id) | 234 | const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id) |
245 | if (!share) { | 235 | if (!share) { |
@@ -248,7 +238,6 @@ const videosShareValidator = [ | |||
248 | } | 238 | } |
249 | 239 | ||
250 | res.locals.videoShare = share | 240 | res.locals.videoShare = share |
251 | |||
252 | return next() | 241 | return next() |
253 | } | 242 | } |
254 | ] | 243 | ] |
@@ -270,24 +259,25 @@ export { | |||
270 | 259 | ||
271 | // --------------------------------------------------------------------------- | 260 | // --------------------------------------------------------------------------- |
272 | 261 | ||
273 | function checkUserCanDeleteVideo (user: UserInstance, res: express.Response, callback: () => void) { | 262 | function checkUserCanDeleteVideo (user: UserInstance, video: VideoInstance, res: express.Response) { |
274 | // Retrieve the user who did the request | 263 | // Retrieve the user who did the request |
275 | if (res.locals.video.isOwned() === false) { | 264 | if (video.isOwned() === false) { |
276 | return res.status(403) | 265 | res.status(403) |
277 | .json({ error: 'Cannot remove video of another server, blacklist it' }) | 266 | .json({ error: 'Cannot remove video of another server, blacklist it' }) |
278 | .end() | 267 | .end() |
268 | return false | ||
279 | } | 269 | } |
280 | 270 | ||
281 | // Check if the user can delete the video | 271 | // Check if the user can delete the video |
282 | // The user can delete it if s/he is an admin | 272 | // The user can delete it if s/he is an admin |
283 | // Or if s/he is the video's account | 273 | // Or if s/he is the video's account |
284 | const account = res.locals.video.VideoChannel.Account | 274 | const account = video.VideoChannel.Account |
285 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && account.userId !== user.id) { | 275 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && account.userId !== user.id) { |
286 | return res.status(403) | 276 | res.status(403) |
287 | .json({ error: 'Cannot remove video of another user' }) | 277 | .json({ error: 'Cannot remove video of another user' }) |
288 | .end() | 278 | .end() |
279 | return false | ||
289 | } | 280 | } |
290 | 281 | ||
291 | // If we reach this comment, we can delete the video | 282 | return true |
292 | callback() | ||
293 | } | 283 | } |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index 7852c1c2b..34e62c66d 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -1,37 +1,31 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator/check' | 2 | import { query } from 'express-validator/check' |
3 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' | 3 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' |
4 | import { database as db } from '../../initializers' | ||
5 | import { checkErrors } from './utils' | ||
6 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { database as db } from '../../initializers' | ||
6 | import { areValidationErrors } from './utils' | ||
7 | 7 | ||
8 | const webfingerValidator = [ | 8 | const webfingerValidator = [ |
9 | query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), | 9 | query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), |
10 | 10 | ||
11 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 11 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
12 | logger.debug('Checking webfinger parameters', { parameters: req.query }) | 12 | logger.debug('Checking webfinger parameters', { parameters: req.query }) |
13 | 13 | ||
14 | checkErrors(req, res, () => { | 14 | if (areValidationErrors(req, res)) return |
15 | // Remove 'acct:' from the beginning of the string | 15 | |
16 | const nameWithHost = req.query.resource.substr(5) | 16 | // Remove 'acct:' from the beginning of the string |
17 | const [ name ] = nameWithHost.split('@') | 17 | const nameWithHost = req.query.resource.substr(5) |
18 | 18 | const [ name ] = nameWithHost.split('@') | |
19 | db.Account.loadLocalByName(name) | 19 | |
20 | .then(account => { | 20 | const account = await db.Account.loadLocalByName(name) |
21 | if (!account) { | 21 | if (!account) { |
22 | return res.status(404) | 22 | return res.status(404) |
23 | .send({ error: 'Account not found' }) | 23 | .send({ error: 'Account not found' }) |
24 | .end() | 24 | .end() |
25 | } | 25 | } |
26 | 26 | ||
27 | res.locals.account = account | 27 | res.locals.account = account |
28 | return next() | 28 | return next() |
29 | }) | ||
30 | .catch(err => { | ||
31 | logger.error('Error in webfinger validator.', err) | ||
32 | return res.sendStatus(500) | ||
33 | }) | ||
34 | }) | ||
35 | } | 29 | } |
36 | ] | 30 | ] |
37 | 31 | ||