aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-03-23 11:54:08 +0100
committerChocobozzz <me@florianbigard.com>2021-03-24 18:18:41 +0100
commit4bc45da342597fb49593fc14c40f8dc5a97bb64e (patch)
tree9fa876e21995b27827fbc4467bd71b8d427312e2 /server
parentc0ab041c2c749db05ce564d3078c2ad10d18f35f (diff)
downloadPeerTube-4bc45da342597fb49593fc14c40f8dc5a97bb64e.tar.gz
PeerTube-4bc45da342597fb49593fc14c40f8dc5a97bb64e.tar.zst
PeerTube-4bc45da342597fb49593fc14c40f8dc5a97bb64e.zip
Add hooks support for video download
Diffstat (limited to 'server')
-rw-r--r--server/controllers/download.ts85
-rw-r--r--server/lib/files-cache/videos-torrent-cache.ts13
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js26
-rw-r--r--server/tests/plugins/filter-hooks.ts63
4 files changed, 174 insertions, 13 deletions
diff --git a/server/controllers/download.ts b/server/controllers/download.ts
index 27caa1518..fd44f10e9 100644
--- a/server/controllers/download.ts
+++ b/server/controllers/download.ts
@@ -1,8 +1,10 @@
1import * as cors from 'cors' 1import * as cors from 'cors'
2import * as express from 'express' 2import * as express from 'express'
3import { logger } from '@server/helpers/logger'
3import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' 4import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
5import { Hooks } from '@server/lib/plugins/hooks'
4import { getVideoFilePath } from '@server/lib/video-paths' 6import { getVideoFilePath } from '@server/lib/video-paths'
5import { MVideoFile, MVideoFullLight } from '@server/types/models' 7import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
7import { VideoStreamingPlaylistType } from '@shared/models' 9import { VideoStreamingPlaylistType } from '@shared/models'
8import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' 10import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
@@ -14,19 +16,19 @@ downloadRouter.use(cors())
14 16
15downloadRouter.use( 17downloadRouter.use(
16 STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename', 18 STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename',
17 downloadTorrent 19 asyncMiddleware(downloadTorrent)
18) 20)
19 21
20downloadRouter.use( 22downloadRouter.use(
21 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', 23 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
22 asyncMiddleware(videosDownloadValidator), 24 asyncMiddleware(videosDownloadValidator),
23 downloadVideoFile 25 asyncMiddleware(downloadVideoFile)
24) 26)
25 27
26downloadRouter.use( 28downloadRouter.use(
27 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', 29 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
28 asyncMiddleware(videosDownloadValidator), 30 asyncMiddleware(videosDownloadValidator),
29 downloadHLSVideoFile 31 asyncMiddleware(downloadHLSVideoFile)
30) 32)
31 33
32// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
@@ -41,28 +43,58 @@ async function downloadTorrent (req: express.Request, res: express.Response) {
41 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) 43 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
42 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 44 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
43 45
46 const allowParameters = { torrentPath: result.path, downloadName: result.downloadName }
47
48 const allowedResult = await Hooks.wrapFun(
49 isTorrentDownloadAllowed,
50 allowParameters,
51 'filter:api.download.torrent.allowed.result'
52 )
53
54 if (!checkAllowResult(res, allowParameters, allowedResult)) return
55
44 return res.download(result.path, result.downloadName) 56 return res.download(result.path, result.downloadName)
45} 57}
46 58
47function downloadVideoFile (req: express.Request, res: express.Response) { 59async function downloadVideoFile (req: express.Request, res: express.Response) {
48 const video = res.locals.videoAll 60 const video = res.locals.videoAll
49 61
50 const videoFile = getVideoFile(req, video.VideoFiles) 62 const videoFile = getVideoFile(req, video.VideoFiles)
51 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() 63 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
52 64
65 const allowParameters = { video, videoFile }
66
67 const allowedResult = await Hooks.wrapFun(
68 isVideoDownloadAllowed,
69 allowParameters,
70 'filter:api.download.video.allowed.result'
71 )
72
73 if (!checkAllowResult(res, allowParameters, allowedResult)) return
74
53 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) 75 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`)
54} 76}
55 77
56function downloadHLSVideoFile (req: express.Request, res: express.Response) { 78async function downloadHLSVideoFile (req: express.Request, res: express.Response) {
57 const video = res.locals.videoAll 79 const video = res.locals.videoAll
58 const playlist = getHLSPlaylist(video) 80 const streamingPlaylist = getHLSPlaylist(video)
59 if (!playlist) return res.status(HttpStatusCode.NOT_FOUND_404).end 81 if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
60 82
61 const videoFile = getVideoFile(req, playlist.VideoFiles) 83 const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
62 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() 84 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
63 85
64 const filename = `${video.name}-${videoFile.resolution}p-${playlist.getStringType()}${videoFile.extname}` 86 const allowParameters = { video, streamingPlaylist, videoFile }
65 return res.download(getVideoFilePath(playlist, videoFile), filename) 87
88 const allowedResult = await Hooks.wrapFun(
89 isVideoDownloadAllowed,
90 allowParameters,
91 'filter:api.download.video.allowed.result'
92 )
93
94 if (!checkAllowResult(res, allowParameters, allowedResult)) return
95
96 const filename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}`
97 return res.download(getVideoFilePath(streamingPlaylist, videoFile), filename)
66} 98}
67 99
68function getVideoFile (req: express.Request, files: MVideoFile[]) { 100function getVideoFile (req: express.Request, files: MVideoFile[]) {
@@ -76,3 +108,34 @@ function getHLSPlaylist (video: MVideoFullLight) {
76 108
77 return Object.assign(playlist, { Video: video }) 109 return Object.assign(playlist, { Video: video })
78} 110}
111
112type AllowedResult = {
113 allowed: boolean
114 errorMessage?: string
115}
116
117function isTorrentDownloadAllowed (_object: {
118 torrentPath: string
119}): AllowedResult {
120 return { allowed: true }
121}
122
123function isVideoDownloadAllowed (_object: {
124 video: MVideo
125 videoFile: MVideoFile
126 streamingPlaylist?: MStreamingPlaylist
127}): AllowedResult {
128 return { allowed: true }
129}
130
131function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
132 if (!result || result.allowed !== true) {
133 logger.info('Download is not allowed.', { result, allowParameters })
134 res.status(HttpStatusCode.FORBIDDEN_403)
135 .json({ error: result.errorMessage || 'Refused download' })
136
137 return false
138 }
139
140 return true
141}
diff --git a/server/lib/files-cache/videos-torrent-cache.ts b/server/lib/files-cache/videos-torrent-cache.ts
index 881fa9ced..23217f140 100644
--- a/server/lib/files-cache/videos-torrent-cache.ts
+++ b/server/lib/files-cache/videos-torrent-cache.ts
@@ -5,6 +5,7 @@ import { CONFIG } from '../../initializers/config'
5import { FILES_CACHE } from '../../initializers/constants' 5import { FILES_CACHE } from '../../initializers/constants'
6import { VideoModel } from '../../models/video/video' 6import { VideoModel } from '../../models/video/video'
7import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' 7import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
8import { MVideo, MVideoFile } from '@server/types/models'
8 9
9class VideosTorrentCache extends AbstractVideoStaticFileCache <string> { 10class VideosTorrentCache extends AbstractVideoStaticFileCache <string> {
10 11
@@ -22,7 +23,11 @@ class VideosTorrentCache extends AbstractVideoStaticFileCache <string> {
22 const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename) 23 const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename)
23 if (!file) return undefined 24 if (!file) return undefined
24 25
25 if (file.getVideo().isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename) } 26 if (file.getVideo().isOwned()) {
27 const downloadName = this.buildDownloadName(file.getVideo(), file)
28
29 return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename), downloadName }
30 }
26 31
27 return this.loadRemoteFile(filename) 32 return this.loadRemoteFile(filename)
28 } 33 }
@@ -43,10 +48,14 @@ class VideosTorrentCache extends AbstractVideoStaticFileCache <string> {
43 48
44 await doRequestAndSaveToFile(remoteUrl, destPath) 49 await doRequestAndSaveToFile(remoteUrl, destPath)
45 50
46 const downloadName = `${video.name}-${file.resolution}p.torrent` 51 const downloadName = this.buildDownloadName(video, file)
47 52
48 return { isOwned: false, path: destPath, downloadName } 53 return { isOwned: false, path: destPath, downloadName }
49 } 54 }
55
56 private buildDownloadName (video: MVideo, file: MVideoFile) {
57 return `${video.name}-${file.resolution}p.torrent`
58 }
50} 59}
51 60
52export { 61export {
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index 305d92002..9913d0846 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -184,6 +184,32 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
184 return result 184 return result
185 } 185 }
186 }) 186 })
187
188 registerHook({
189 target: 'filter:api.download.torrent.allowed.result',
190 handler: (result, params) => {
191 if (params && params.downloadName.includes('bad torrent')) {
192 return { allowed: false, errorMessage: 'Liu Bei' }
193 }
194
195 return result
196 }
197 })
198
199 registerHook({
200 target: 'filter:api.download.video.allowed.result',
201 handler: (result, params) => {
202 if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) {
203 return { allowed: false, errorMessage: 'Cao Cao' }
204 }
205
206 if (params && params.streamingPlaylist && params.video.name.includes('bad playlist file')) {
207 return { allowed: false, errorMessage: 'Sun Jian' }
208 }
209
210 return result
211 }
212 })
187} 213}
188 214
189async function unregister () { 215async function unregister () {
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index d88170201..6996ae788 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -20,12 +20,14 @@ import {
20 getVideoThreadComments, 20 getVideoThreadComments,
21 getVideoWithToken, 21 getVideoWithToken,
22 installPlugin, 22 installPlugin,
23 makeRawRequest,
23 registerUser, 24 registerUser,
24 setAccessTokensToServers, 25 setAccessTokensToServers,
25 setDefaultVideoChannel, 26 setDefaultVideoChannel,
26 updateCustomSubConfig, 27 updateCustomSubConfig,
27 updateVideo, 28 updateVideo,
28 uploadVideo, 29 uploadVideo,
30 uploadVideoAndGetId,
29 waitJobs 31 waitJobs
30} from '../../../shared/extra-utils' 32} from '../../../shared/extra-utils'
31import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers' 33import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
@@ -355,6 +357,67 @@ describe('Test plugin filter hooks', function () {
355 }) 357 })
356 }) 358 })
357 359
360 describe('Download hooks', function () {
361 const downloadVideos: VideoDetails[] = []
362
363 before(async function () {
364 this.timeout(60000)
365
366 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
367 transcoding: {
368 webtorrent: {
369 enabled: true
370 },
371 hls: {
372 enabled: true
373 }
374 }
375 })
376
377 const uuids: string[] = []
378
379 for (const name of [ 'bad torrent', 'bad file', 'bad playlist file' ]) {
380 const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid
381 uuids.push(uuid)
382 }
383
384 await waitJobs(servers)
385
386 for (const uuid of uuids) {
387 const res = await getVideo(servers[0].url, uuid)
388 downloadVideos.push(res.body)
389 }
390 })
391
392 it('Should run filter:api.download.torrent.allowed.result', async function () {
393 const res = await makeRawRequest(downloadVideos[0].files[0].torrentDownloadUrl, 403)
394 expect(res.body.error).to.equal('Liu Bei')
395
396 await makeRawRequest(downloadVideos[1].files[0].torrentDownloadUrl, 200)
397 await makeRawRequest(downloadVideos[2].files[0].torrentDownloadUrl, 200)
398 })
399
400 it('Should run filter:api.download.video.allowed.result', async function () {
401 {
402 const res = await makeRawRequest(downloadVideos[1].files[0].fileDownloadUrl, 403)
403 expect(res.body.error).to.equal('Cao Cao')
404
405 await makeRawRequest(downloadVideos[0].files[0].fileDownloadUrl, 200)
406 await makeRawRequest(downloadVideos[2].files[0].fileDownloadUrl, 200)
407 }
408
409 {
410 const res = await makeRawRequest(downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl, 403)
411 expect(res.body.error).to.equal('Sun Jian')
412
413 await makeRawRequest(downloadVideos[2].files[0].fileDownloadUrl, 200)
414
415 await makeRawRequest(downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, 200)
416 await makeRawRequest(downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl, 200)
417 }
418 })
419 })
420
358 after(async function () { 421 after(async function () {
359 await cleanupTests(servers) 422 await cleanupTests(servers)
360 }) 423 })