From 4638cd713dcdd007cd7f49b9a95fa62ac7823e7c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 15 Nov 2022 14:41:55 +0100 Subject: [PATCH] Don't inject untrusted input Even if it's already checked in middlewares It's better to have safe modals too --- .../manager-options-builder.ts | 2 -- server/controllers/api/users/my-history.ts | 3 ++- .../api/users/my-video-playlists.ts | 5 +++-- server/controllers/api/video-playlist.ts | 5 +++-- server/controllers/api/videos/update.ts | 3 ++- server/controllers/download.ts | 4 ++-- server/controllers/services.ts | 5 +++-- .../helpers/custom-validators/video-studio.ts | 3 ++- server/helpers/video.ts | 5 +++-- server/lib/activitypub/collection.ts | 3 ++- server/middlewares/pagination.ts | 5 +++-- server/middlewares/validators/abuse.ts | 3 ++- server/middlewares/validators/redundancy.ts | 3 ++- .../middlewares/validators/shared/abuses.ts | 3 ++- .../middlewares/validators/shared/accounts.ts | 5 +++-- server/middlewares/validators/shared/users.ts | 3 ++- .../validators/shared/video-comments.ts | 7 ++++--- .../validators/shared/video-ownerships.ts | 3 ++- server/middlewares/validators/users.ts | 3 ++- .../validators/videos/video-imports.ts | 3 ++- .../validators/videos/video-playlists.ts | 3 ++- server/models/abuse/abuse-query-builder.ts | 5 +++-- server/models/actor/actor.ts | 4 ++-- server/models/user/user-notification.ts | 3 ++- server/models/user/user.ts | 15 ++++++------- server/models/utils.ts | 5 +++-- .../sql/video/videos-id-list-query-builder.ts | 5 +++-- server/models/video/video-channel.ts | 4 ++-- server/models/video/video-playlist-element.ts | 21 ++++++++++++------- server/models/video/video-share.ts | 5 +++-- server/tools/peertube-redundancy.ts | 4 ++-- shared/core-utils/common/index.ts | 1 + shared/core-utils/common/number.ts | 7 +++++++ shared/extra-utils/ffprobe.ts | 3 ++- shared/server-commands/miscs/sql-command.ts | 3 ++- 35 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 shared/core-utils/common/number.ts diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts index 4d227eb2a..c820d637b 100644 --- a/client/src/assets/player/shared/manager-options/manager-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts @@ -105,8 +105,6 @@ export class ManagerOptionsBuilder { Object.assign(videojsOptions, { language: commonOptions.language }) } - console.log(videojsOptions) - return videojsOptions } diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index bc5b40f59..e6d3e86ac 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts @@ -1,3 +1,4 @@ +import { forceNumber } from '@shared/core-utils' import express from 'express' import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { getFormattedObjects } from '../../../helpers/utils' @@ -55,7 +56,7 @@ async function listMyVideosHistory (req: express.Request, res: express.Response) async function removeUserHistoryElement (req: express.Request, res: express.Response) { const user = res.locals.oauth.token.User - await UserVideoHistoryModel.removeUserHistoryElement(user, parseInt(req.params.videoId + '')) + await UserVideoHistoryModel.removeUserHistoryElement(user, forceNumber(req.params.videoId)) return res.sendStatus(HttpStatusCode.NO_CONTENT_204) } diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts index 715717610..fbdbb7e50 100644 --- a/server/controllers/api/users/my-video-playlists.ts +++ b/server/controllers/api/users/my-video-playlists.ts @@ -1,5 +1,6 @@ -import { uuidToShort } from '@shared/extra-utils' import express from 'express' +import { forceNumber } from '@shared/core-utils' +import { uuidToShort } from '@shared/extra-utils' import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' import { asyncMiddleware, authenticate } from '../../../middlewares' import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists' @@ -22,7 +23,7 @@ export { // --------------------------------------------------------------------------- async function doVideosInPlaylistExist (req: express.Request, res: express.Response) { - const videoIds = req.query.videoIds.map(i => parseInt(i + '', 10)) + const videoIds = req.query.videoIds.map(i => forceNumber(i)) const user = res.locals.oauth.token.User const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds) diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 1255d14c6..67fac3751 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts @@ -46,6 +46,7 @@ import { import { AccountModel } from '../../models/account/account' import { VideoPlaylistModel } from '../../models/video/video-playlist' import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' +import { forceNumber } from '@shared/core-utils' const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) @@ -245,7 +246,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response) if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description if (videoPlaylistInfoToUpdate.privacy !== undefined) { - videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10) + videoPlaylistInstance.privacy = forceNumber(videoPlaylistInfoToUpdate.privacy) if (wasNotPrivatePlaylist === true && videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE) { await sendDeleteVideoPlaylist(videoPlaylistInstance, t) @@ -424,7 +425,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons const endOldPosition = oldPosition + reorderLength - 1 // Insert our reordered elements in their place (update) - await VideoPlaylistElementModel.reassignPositionOf(videoPlaylist.id, oldPosition, endOldPosition, newPosition, t) + await VideoPlaylistElementModel.reassignPositionOf({ videoPlaylistId: videoPlaylist.id, firstPosition: oldPosition, endPosition: endOldPosition, newPosition, transaction: t }) // Decrease positions of elements after the old position of our ordered elements (decrease) await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, -reorderLength, t) diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts index 0a910379a..260dee2b9 100644 --- a/server/controllers/api/videos/update.ts +++ b/server/controllers/api/videos/update.ts @@ -19,6 +19,7 @@ import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosU import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' import { VideoModel } from '../../../models/video/video' import { VideoPathManager } from '@server/lib/video-path-manager' +import { forceNumber } from '@shared/core-utils' const lTags = loggerTagsFactory('api', 'video') const auditLogger = auditLoggerFactory('videos') @@ -174,7 +175,7 @@ async function updateVideoPrivacy (options: { const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) - const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) + const newPrivacy = forceNumber(videoInfoToUpdate.privacy) setVideoPrivacy(videoInstance, newPrivacy) // Unfederate the video if the new privacy is not compatible with federation diff --git a/server/controllers/download.ts b/server/controllers/download.ts index d9f34109f..65b9a1d1b 100644 --- a/server/controllers/download.ts +++ b/server/controllers/download.ts @@ -5,7 +5,7 @@ import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache import { Hooks } from '@server/lib/plugins/hooks' import { VideoPathManager } from '@server/lib/video-path-manager' import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' -import { addQueryParams } from '@shared/core-utils' +import { addQueryParams, forceNumber } from '@shared/core-utils' import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares' @@ -132,7 +132,7 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response } function getVideoFile (req: express.Request, files: MVideoFile[]) { - const resolution = parseInt(req.params.resolution, 10) + const resolution = forceNumber(req.params.resolution) return files.find(f => f.resolution === resolution) } diff --git a/server/controllers/services.ts b/server/controllers/services.ts index cabcbc00b..7c7ca1ff3 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts @@ -4,6 +4,7 @@ import { escapeHTML } from '@shared/core-utils/renderer' import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' import { asyncMiddleware, oembedValidator } from '../middlewares' import { accountNameWithHostGetValidator } from '../middlewares/validators' +import { forceNumber } from '@shared/core-utils' const servicesRouter = express.Router() @@ -108,8 +109,8 @@ function buildOEmbed (options: { const { req, previewSize, previewPath, title, channel, embedPath } = options const webserverUrl = WEBSERVER.URL - const maxHeight = parseInt(req.query.maxheight, 10) - const maxWidth = parseInt(req.query.maxwidth, 10) + const maxHeight = forceNumber(req.query.maxheight) + const maxWidth = forceNumber(req.query.maxwidth) const embedUrl = webserverUrl + embedPath const embedTitle = escapeHTML(title) diff --git a/server/helpers/custom-validators/video-studio.ts b/server/helpers/custom-validators/video-studio.ts index 19e7906d5..68dfec8dd 100644 --- a/server/helpers/custom-validators/video-studio.ts +++ b/server/helpers/custom-validators/video-studio.ts @@ -4,6 +4,7 @@ import { buildTaskFileFieldname } from '@server/lib/video-studio' import { VideoStudioTask } from '@shared/models' import { isArray } from './misc' import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos' +import { forceNumber } from '@shared/core-utils' function isValidStudioTasksArray (tasks: any) { if (!isArray(tasks)) return false @@ -24,7 +25,7 @@ function isStudioCutTaskValid (task: VideoStudioTask) { if (!start || !end) return true - return parseInt(start + '') < parseInt(end + '') + return forceNumber(start) < forceNumber(end) } function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) { diff --git a/server/helpers/video.ts b/server/helpers/video.ts index f5f645d3e..c688ef1e3 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts @@ -2,6 +2,7 @@ import { Response } from 'express' import { CONFIG } from '@server/initializers/config' import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models' import { VideoPrivacy, VideoState } from '@shared/models' +import { forceNumber } from '@shared/core-utils' function getVideoWithAttributes (res: Response) { return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo @@ -14,14 +15,14 @@ function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) { } function isPrivacyForFederation (privacy: VideoPrivacy) { - const castedPrivacy = parseInt(privacy + '', 10) + const castedPrivacy = forceNumber(privacy) return castedPrivacy === VideoPrivacy.PUBLIC || (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED) } function isStateForFederation (state: VideoState) { - const castedState = parseInt(state + '', 10) + const castedState = forceNumber(state) return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED } diff --git a/server/lib/activitypub/collection.ts b/server/lib/activitypub/collection.ts index f897141ea..a176cab51 100644 --- a/server/lib/activitypub/collection.ts +++ b/server/lib/activitypub/collection.ts @@ -3,6 +3,7 @@ import validator from 'validator' import { pageToStartAndCount } from '@server/helpers/core-utils' import { ACTIVITY_PUB } from '@server/initializers/constants' import { ResultList } from '@shared/models' +import { forceNumber } from '@shared/core-utils' type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird> | Promise> @@ -33,7 +34,7 @@ async function activityPubCollectionPagination ( let prev: string | undefined // Assert page is a number - page = parseInt(page, 10) + page = forceNumber(page) // There are more results if (result.total > page * size) { diff --git a/server/middlewares/pagination.ts b/server/middlewares/pagination.ts index 9812af9e4..17e43f743 100644 --- a/server/middlewares/pagination.ts +++ b/server/middlewares/pagination.ts @@ -1,12 +1,13 @@ import express from 'express' +import { forceNumber } from '@shared/core-utils' import { PAGINATION } from '../initializers/constants' function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) { if (!req.query.start) req.query.start = 0 - else req.query.start = parseInt(req.query.start, 10) + else req.query.start = forceNumber(req.query.start) if (!req.query.count) req.query.count = PAGINATION.GLOBAL.COUNT.DEFAULT - else req.query.count = parseInt(req.query.count, 10) + else req.query.count = forceNumber(req.query.count) return next() } diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts index 9b94008ce..70bae1775 100644 --- a/server/middlewares/validators/abuse.ts +++ b/server/middlewares/validators/abuse.ts @@ -18,6 +18,7 @@ import { AbuseMessageModel } from '@server/models/abuse/abuse-message' import { AbuseCreate, UserRight } from '@shared/models' import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared' +import { forceNumber } from '@shared/core-utils' const abuseReportValidator = [ body('account.id') @@ -216,7 +217,7 @@ const deleteAbuseMessageValidator = [ const user = res.locals.oauth.token.user const abuse = res.locals.abuse - const messageId = parseInt(req.params.messageId + '', 10) + const messageId = forceNumber(req.params.messageId) const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) if (!abuseMessage) { diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index 79460f63c..c80f9b728 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts @@ -1,6 +1,7 @@ import express from 'express' import { body, param, query } from 'express-validator' import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' import { exists, @@ -171,7 +172,7 @@ const removeVideoRedundancyValidator = [ async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return - const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) + const redundancy = await VideoRedundancyModel.loadByIdWithVideo(forceNumber(req.params.redundancyId)) if (!redundancy) { return res.fail({ status: HttpStatusCode.NOT_FOUND_404, diff --git a/server/middlewares/validators/shared/abuses.ts b/server/middlewares/validators/shared/abuses.ts index 2b8d86ba5..2c988f9ec 100644 --- a/server/middlewares/validators/shared/abuses.ts +++ b/server/middlewares/validators/shared/abuses.ts @@ -1,9 +1,10 @@ import { Response } from 'express' import { AbuseModel } from '@server/models/abuse/abuse' import { HttpStatusCode } from '@shared/models' +import { forceNumber } from '@shared/core-utils' async function doesAbuseExist (abuseId: number | string, res: Response) { - const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10)) + const abuse = await AbuseModel.loadByIdWithReporter(forceNumber(abuseId)) if (!abuse) { res.fail({ diff --git a/server/middlewares/validators/shared/accounts.ts b/server/middlewares/validators/shared/accounts.ts index fe4f83aa0..72b0e235e 100644 --- a/server/middlewares/validators/shared/accounts.ts +++ b/server/middlewares/validators/shared/accounts.ts @@ -2,10 +2,11 @@ import { Response } from 'express' import { AccountModel } from '@server/models/account/account' import { UserModel } from '@server/models/user/user' import { MAccountDefault } from '@server/types/models' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode } from '@shared/models' function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { - const promise = AccountModel.load(parseInt(id + '', 10)) + const promise = AccountModel.load(forceNumber(id)) return doesAccountExist(promise, res, sendNotFound) } @@ -40,7 +41,7 @@ async function doesAccountExist (p: Promise, res: Response, sen } async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) { - const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) + const user = await UserModel.loadByIdWithChannels(forceNumber(id)) if (token !== user.feedToken) { res.fail({ diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts index fbaa7db0e..b8f1436d3 100644 --- a/server/middlewares/validators/shared/users.ts +++ b/server/middlewares/validators/shared/users.ts @@ -2,10 +2,11 @@ import express from 'express' import { ActorModel } from '@server/models/actor/actor' import { UserModel } from '@server/models/user/user' import { MUserDefault } from '@server/types/models' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode } from '@shared/models' function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) { - const id = parseInt(idArg + '', 10) + const id = forceNumber(idArg) return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res) } diff --git a/server/middlewares/validators/shared/video-comments.ts b/server/middlewares/validators/shared/video-comments.ts index 8d1a16294..0961b3ec9 100644 --- a/server/middlewares/validators/shared/video-comments.ts +++ b/server/middlewares/validators/shared/video-comments.ts @@ -1,10 +1,11 @@ import express from 'express' import { VideoCommentModel } from '@server/models/video/video-comment' import { MVideoId } from '@server/types/models' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode, ServerErrorCode } from '@shared/models' async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) { - const id = parseInt(idArg + '', 10) + const id = forceNumber(idArg) const videoComment = await VideoCommentModel.loadById(id) if (!videoComment) { @@ -33,7 +34,7 @@ async function doesVideoCommentThreadExist (idArg: number | string, video: MVide } async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) { - const id = parseInt(idArg + '', 10) + const id = forceNumber(idArg) const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) if (!videoComment) { @@ -57,7 +58,7 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r } async function doesCommentIdExist (idArg: number | string, res: express.Response) { - const id = parseInt(idArg + '', 10) + const id = forceNumber(idArg) const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) if (!videoComment) { diff --git a/server/middlewares/validators/shared/video-ownerships.ts b/server/middlewares/validators/shared/video-ownerships.ts index 680613cda..33ac9c8b6 100644 --- a/server/middlewares/validators/shared/video-ownerships.ts +++ b/server/middlewares/validators/shared/video-ownerships.ts @@ -1,9 +1,10 @@ import express from 'express' import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode } from '@shared/models' async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) { - const id = parseInt(idArg + '', 10) + const id = forceNumber(idArg) const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) if (!videoChangeOwnership) { diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 055af3b64..50327b6ae 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts @@ -1,6 +1,7 @@ import express from 'express' import { body, param, query } from 'express-validator' import { Hooks } from '@server/lib/plugins/hooks' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' import { isThemeNameValid } from '../../helpers/custom-validators/plugins' @@ -515,7 +516,7 @@ const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Requ const user = res.locals.oauth.token.User const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR - const targetUserId = parseInt(targetUserIdGetter(req) + '') + const targetUserId = forceNumber(targetUserIdGetter(req)) // Admin/moderator action on another user, skip the password check if (isAdminOrModerator && targetUserId !== user.id) { diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index f295b1885..72442aeb6 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts @@ -4,6 +4,7 @@ import { isResolvingToUnicastOnly } from '@server/helpers/dns' import { isPreImportVideoAccepted } from '@server/lib/moderation' import { Hooks } from '@server/lib/plugins/hooks' import { MUserAccountId, MVideoImport } from '@server/types/models' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models' import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' @@ -130,7 +131,7 @@ const videoImportCancelValidator = [ async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return - if (!await doesVideoImportExist(parseInt(req.params.id), res)) return + if (!await doesVideoImportExist(forceNumber(req.params.id), res)) return if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return if (res.locals.videoImport.state !== VideoImportState.PENDING) { diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 6d4b8a6f1..e4b7e5c56 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts @@ -2,6 +2,7 @@ import express from 'express' import { body, param, query, ValidationChain } from 'express-validator' import { ExpressPromiseHandler } from '@server/types/express-handler' import { MUserAccountId } from '@server/types/models' +import { forceNumber } from '@shared/core-utils' import { HttpStatusCode, UserRight, @@ -258,7 +259,7 @@ const videoPlaylistElementAPGetValidator = [ async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return - const playlistElementId = parseInt(req.params.playlistElementId + '', 10) + const playlistElementId = forceNumber(req.params.playlistElementId) const playlistId = req.params.playlistId const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) diff --git a/server/models/abuse/abuse-query-builder.ts b/server/models/abuse/abuse-query-builder.ts index cfc924ba4..74f4542e5 100644 --- a/server/models/abuse/abuse-query-builder.ts +++ b/server/models/abuse/abuse-query-builder.ts @@ -1,5 +1,6 @@ import { exists } from '@server/helpers/custom-validators/misc' +import { forceNumber } from '@shared/core-utils' import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models' import { buildBlockedAccountSQL, buildDirectionAndField } from '../utils' @@ -135,12 +136,12 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | } if (exists(options.count)) { - const count = parseInt(options.count + '', 10) + const count = forceNumber(options.count) suffix += `LIMIT ${count} ` } if (exists(options.start)) { - const start = parseInt(options.start + '', 10) + const start = forceNumber(options.start) suffix += `OFFSET ${start} ` } } diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts index 88db241dc..d7afa727d 100644 --- a/server/models/actor/actor.ts +++ b/server/models/actor/actor.ts @@ -18,7 +18,7 @@ import { import { activityPubContextify } from '@server/lib/activitypub/context' import { getBiggestActorImage } from '@server/lib/actor-image' import { ModelCache } from '@server/models/model-cache' -import { getLowercaseExtension } from '@shared/core-utils' +import { forceNumber, getLowercaseExtension } from '@shared/core-utils' import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models' import { AttributesOnly } from '@shared/typescript-utils' import { @@ -446,7 +446,7 @@ export class ActorModel extends Model>> { } static rebuildFollowsCount (ofId: number, type: 'followers' | 'following', transaction?: Transaction) { - const sanitizedOfId = parseInt(ofId + '', 10) + const sanitizedOfId = forceNumber(ofId) const where = { id: sanitizedOfId } let columnToUpdate: string diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts index 6209cb4bf..d37fa5dc7 100644 --- a/server/models/user/user-notification.ts +++ b/server/models/user/user-notification.ts @@ -2,6 +2,7 @@ import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize' import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' import { getBiggestActorImage } from '@server/lib/actor-image' import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' +import { forceNumber } from '@shared/core-utils' import { uuidToShort } from '@shared/extra-utils' import { UserNotification, UserNotificationType } from '@shared/models' import { AttributesOnly } from '@shared/typescript-utils' @@ -284,7 +285,7 @@ export class UserNotificationModel extends Model>> { videoQuotaDaily: this.videoQuotaDaily, videoQuotaUsed: videoQuotaUsed !== undefined - ? parseInt(videoQuotaUsed + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) + ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) : undefined, videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined - ? parseInt(videoQuotaUsedDaily + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) + ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) : undefined, videosCount: videosCount !== undefined - ? parseInt(videosCount + '', 10) + ? forceNumber(videosCount) : undefined, abusesCount: abusesCount - ? parseInt(abusesCount, 10) + ? forceNumber(abusesCount) : undefined, abusesAcceptedCount: abusesAcceptedCount - ? parseInt(abusesAcceptedCount, 10) + ? forceNumber(abusesAcceptedCount) : undefined, abusesCreatedCount: abusesCreatedCount !== undefined - ? parseInt(abusesCreatedCount + '', 10) + ? forceNumber(abusesCreatedCount) : undefined, videoCommentsCount: videoCommentsCount !== undefined - ? parseInt(videoCommentsCount + '', 10) + ? forceNumber(videoCommentsCount) : undefined, noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, diff --git a/server/models/utils.ts b/server/models/utils.ts index 1e168d419..3476799ce 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -1,5 +1,6 @@ import { literal, Op, OrderItem, Sequelize } from 'sequelize' import validator from 'validator' +import { forceNumber } from '@shared/core-utils' type SortType = { sortModel: string, sortValue: string } @@ -202,7 +203,7 @@ function buildBlockedAccountSQLOptimized (columnNameJoin: string, blockerIds: nu } function buildServerIdsFollowedBy (actorId: any) { - const actorIdNumber = parseInt(actorId + '', 10) + const actorIdNumber = forceNumber(actorId) return '(' + 'SELECT "actor"."serverId" FROM "actorFollow" ' + @@ -218,7 +219,7 @@ function buildWhereIdOrUUID (id: number | string) { function parseAggregateResult (result: any) { if (!result) return 0 - const total = parseInt(result + '', 10) + const total = forceNumber(result) if (isNaN(total)) return 0 return total diff --git a/server/models/video/sql/video/videos-id-list-query-builder.ts b/server/models/video/sql/video/videos-id-list-query-builder.ts index 14f903851..7c864bf27 100644 --- a/server/models/video/sql/video/videos-id-list-query-builder.ts +++ b/server/models/video/sql/video/videos-id-list-query-builder.ts @@ -6,6 +6,7 @@ import { buildDirectionAndField, createSafeIn, parseRowCountResult } from '@serv import { MUserAccountId, MUserId } from '@server/types/models' import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models' import { AbstractRunQuery } from '../../../shared/abstract-run-query' +import { forceNumber } from '@shared/core-utils' /** * @@ -689,12 +690,12 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery { } private setLimit (countArg: number) { - const count = parseInt(countArg + '', 10) + const count = forceNumber(countArg) this.limit = `LIMIT ${count}` } private setOffset (startArg: number) { - const start = parseInt(startArg + '', 10) + const start = forceNumber(startArg) this.offset = `OFFSET ${start}` } } diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 91dafbcf1..9e461b6ca 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -19,7 +19,7 @@ import { } from 'sequelize-typescript' import { CONFIG } from '@server/initializers/config' import { MAccountActor } from '@server/types/models' -import { pick } from '@shared/core-utils' +import { forceNumber, pick } from '@shared/core-utils' import { AttributesOnly } from '@shared/typescript-utils' import { ActivityPubActor } from '../../../shared/models/activitypub' import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' @@ -280,7 +280,7 @@ export type SummaryOptions = { ] }, [ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => { - const daysPrior = parseInt(options.daysPrior + '', 10) + const daysPrior = forceNumber(options.daysPrior) return { attributes: { diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index b45f15bd6..7181b5599 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts @@ -23,6 +23,7 @@ import { MVideoPlaylistElementVideoUrlPlaylistPrivacy, MVideoPlaylistVideoThumbnail } from '@server/types/models/video/video-playlist-element' +import { forceNumber } from '@shared/core-utils' import { AttributesOnly } from '@shared/typescript-utils' import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' import { VideoPrivacy } from '../../../shared/models/videos' @@ -185,7 +186,9 @@ export class VideoPlaylistElementModel extends Model { - const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } + const playlistWhere = validator.isUUID('' + playlistId) + ? { uuid: playlistId } + : { id: playlistId } const query = { include: [ @@ -262,13 +265,15 @@ export class VideoPlaylistElementModel extends Model position ? position + 1 : 1) } - static reassignPositionOf ( - videoPlaylistId: number, - firstPosition: number, - endPosition: number, - newPosition: number, + static reassignPositionOf (options: { + videoPlaylistId: number + firstPosition: number + endPosition: number + newPosition: number transaction?: Transaction - ) { + }) { + const { videoPlaylistId, firstPosition, endPosition, newPosition, transaction } = options + const query = { where: { videoPlaylistId, @@ -281,7 +286,7 @@ export class VideoPlaylistElementModel extends Model { - const safeOwnerId = parseInt(actorOwnerId + '', 10) + const safeOwnerId = forceNumber(actorOwnerId) // /!\ On actor model const query = { @@ -148,7 +149,7 @@ export class VideoShareModel extends Model { - const safeChannelId = parseInt(videoChannelId + '', 10) + const safeChannelId = forceNumber(videoChannelId) // /!\ On actor model const query = { diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts index 4bb9fbc5a..5c82fa420 100644 --- a/server/tools/peertube-redundancy.ts +++ b/server/tools/peertube-redundancy.ts @@ -2,7 +2,7 @@ import CliTable3 from 'cli-table3' import { Command, program } from 'commander' import { URL } from 'url' import validator from 'validator' -import { uniqify } from '@shared/core-utils' +import { forceNumber, uniqify } from '@shared/core-utils' import { HttpStatusCode, VideoRedundanciesTarget } from '@shared/models' import { assignToken, buildServer, getServerCredentials } from './cli' @@ -138,7 +138,7 @@ async function removeRedundancyCLI (options: { video: number }, command: Command process.exit(-1) } - const videoId = parseInt(options.video + '', 10) + const videoId = forceNumber(options.video) const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' }) let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id) diff --git a/shared/core-utils/common/index.ts b/shared/core-utils/common/index.ts index 720977ead..8d63ee1b2 100644 --- a/shared/core-utils/common/index.ts +++ b/shared/core-utils/common/index.ts @@ -2,6 +2,7 @@ export * from './array' export * from './random' export * from './date' export * from './env' +export * from './number' export * from './object' export * from './path' export * from './regexp' diff --git a/shared/core-utils/common/number.ts b/shared/core-utils/common/number.ts new file mode 100644 index 000000000..9a96dcf5c --- /dev/null +++ b/shared/core-utils/common/number.ts @@ -0,0 +1,7 @@ +function forceNumber (value: any) { + return parseInt(value + '') +} + +export { + forceNumber +} diff --git a/shared/extra-utils/ffprobe.ts b/shared/extra-utils/ffprobe.ts index b8e9f4c18..7efc58a0d 100644 --- a/shared/extra-utils/ffprobe.ts +++ b/shared/extra-utils/ffprobe.ts @@ -1,4 +1,5 @@ import { ffprobe, FfprobeData } from 'fluent-ffmpeg' +import { forceNumber } from '@shared/core-utils' import { VideoFileMetadata, VideoResolution } from '@shared/models/videos' /** @@ -55,7 +56,7 @@ async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) { return { absolutePath: data.format.filename, audioStream, - bitrate: parseInt(audioStream['bit_rate'] + '', 10) + bitrate: forceNumber(audioStream['bit_rate']) } } } diff --git a/shared/server-commands/miscs/sql-command.ts b/shared/server-commands/miscs/sql-command.ts index b0d9ce56d..f163cc8c9 100644 --- a/shared/server-commands/miscs/sql-command.ts +++ b/shared/server-commands/miscs/sql-command.ts @@ -1,4 +1,5 @@ import { QueryTypes, Sequelize } from 'sequelize' +import { forceNumber } from '@shared/core-utils' import { AbstractCommand } from '../shared' export class SQLCommand extends AbstractCommand { @@ -63,7 +64,7 @@ export class SQLCommand extends AbstractCommand { if (!total) return 0 - return parseInt(total + '', 10) + return forceNumber(total) } getActorImage (filename: string) {