diff --git a/packages/tests/src/api/check-params/static.ts b/packages/tests/src/api/check-params/static.ts index a07d2c829..12136a2ac 100644 --- a/packages/tests/src/api/check-params/static.ts +++ b/packages/tests/src/api/check-params/static.ts @@ -61,7 +61,8 @@ describe('Test static endpoints validators', function () { await makeGetRequest({ url: server.url, token: server.accessToken, - path: '/static/streaming-playlists/hls/private/' + privateVideo.uuid + '/' + privateM3U8.replace('.m3u8', '.mp4') + path: '/static/streaming-playlists/hls/private/' + privateVideo.uuid + '/' + privateM3U8.replace('.m3u8', '.mp4'), + expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) diff --git a/server/core/controllers/static.ts b/server/core/controllers/static.ts index d29c4db58..10479caf4 100644 --- a/server/core/controllers/static.ts +++ b/server/core/controllers/static.ts @@ -5,7 +5,9 @@ import { ensureCanAccessPrivateVideoHLSFiles, ensureCanAccessVideoPrivateWebVideoFiles, handleStaticError, - optionalAuthenticate + optionalAuthenticate, + privateHLSFileValidator, + privateM3U8PlaylistValidator } from '@server/middlewares/index.js' import cors from 'cors' import express from 'express' @@ -55,17 +57,20 @@ const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AU : [] staticRouter.use( - STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistNameWithoutExtension.m3u8', + STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistNameWithoutExtension([a-z0-9-]+).m3u8', + privateM3U8PlaylistValidator, ...privateHLSStaticMiddlewares, asyncMiddleware(servePrivateM3U8) ) staticRouter.use( - STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, + STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename', + privateHLSFileValidator, ...privateHLSStaticMiddlewares, - express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }), - handleStaticError + servePrivateHLSFile ) +// --------------------------------------------------------------------------- + staticRouter.use( STATIC_PATHS.STREAMING_PLAYLISTS.HLS, express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC, { fallthrough: false }), @@ -80,6 +85,12 @@ export { // --------------------------------------------------------------------------- +function servePrivateHLSFile (req: express.Request, res: express.Response) { + const path = join(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, req.params.videoUUID, req.params.filename) + + return res.sendFile(path) +} + async function servePrivateM3U8 (req: express.Request, res: express.Response) { const path = join(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, req.params.videoUUID, req.params.playlistNameWithoutExtension + '.m3u8') const filename = req.params.playlistNameWithoutExtension + '.m3u8' diff --git a/server/core/middlewares/validators/static.ts b/server/core/middlewares/validators/static.ts index b0b1e98ff..f186d36dc 100644 --- a/server/core/middlewares/validators/static.ts +++ b/server/core/middlewares/validators/static.ts @@ -1,6 +1,7 @@ import { HttpStatusCode } from '@peertube/peertube-models' import { exists, + isSafeFilename, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull @@ -28,7 +29,7 @@ const staticFileTokenBypass = new LRUCache({ ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL }) -const ensureCanAccessVideoPrivateWebVideoFiles = [ +export const ensureCanAccessVideoPrivateWebVideoFiles = [ query('videoFileToken').optional().custom(exists), isValidVideoPasswordHeader(), @@ -67,23 +68,44 @@ const ensureCanAccessVideoPrivateWebVideoFiles = [ } ] -const ensureCanAccessPrivateVideoHLSFiles = [ +export const privateM3U8PlaylistValidator = [ param('videoUUID') .custom(isUUIDValid), param('playlistNameWithoutExtension') - .optional() .custom(v => isSafePeerTubeFilenameWithoutExtension(v)), - query('videoFileToken') - .optional() - .custom(exists), - query('reinjectVideoFileToken') .optional() .customSanitizer(toBooleanOrNull) .isBoolean().withMessage('Should be a valid reinjectVideoFileToken boolean'), + (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + + return next() + } +] + +export const privateHLSFileValidator = [ + param('videoUUID') + .custom(isUUIDValid), + + param('filename') + .custom(v => isSafeFilename(v)), + + (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + + return next() + } +] + +export const ensureCanAccessPrivateVideoHLSFiles = [ + query('videoFileToken') + .optional() + .custom(exists), + isValidVideoPasswordHeader(), async (req: express.Request, res: express.Response, next: express.NextFunction) => { @@ -124,11 +146,6 @@ const ensureCanAccessPrivateVideoHLSFiles = [ } ] -export { - ensureCanAccessPrivateVideoHLSFiles, - ensureCanAccessVideoPrivateWebVideoFiles -} - // --------------------------------------------------------------------------- async function isWebVideoAllowed (req: express.Request, res: express.Response) {