]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/download.ts
Bumped to version v5.2.1
[github/Chocobozzz/PeerTube.git] / server / controllers / download.ts
CommitLineData
41fb13c3
C
1import cors from 'cors'
2import express from 'express'
4bc45da3 3import { logger } from '@server/helpers/logger'
90a8bd30 4import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
4bc45da3 5import { Hooks } from '@server/lib/plugins/hooks'
0305db28 6import { VideoPathManager } from '@server/lib/video-path-manager'
4bc45da3 7import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
4638cd71 8import { addQueryParams, forceNumber } from '@shared/core-utils'
0305db28 9import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
90a8bd30 10import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
3545e72c 11import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares'
90a8bd30
C
12
13const downloadRouter = express.Router()
14
15downloadRouter.use(cors())
16
17downloadRouter.use(
18 STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename',
4bc45da3 19 asyncMiddleware(downloadTorrent)
90a8bd30
C
20)
21
22downloadRouter.use(
23 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
3545e72c 24 optionalAuthenticate,
90a8bd30 25 asyncMiddleware(videosDownloadValidator),
4bc45da3 26 asyncMiddleware(downloadVideoFile)
90a8bd30
C
27)
28
29downloadRouter.use(
30 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
3545e72c 31 optionalAuthenticate,
90a8bd30 32 asyncMiddleware(videosDownloadValidator),
4bc45da3 33 asyncMiddleware(downloadHLSVideoFile)
90a8bd30
C
34)
35
36// ---------------------------------------------------------------------------
37
38export {
39 downloadRouter
40}
41
42// ---------------------------------------------------------------------------
43
44async function downloadTorrent (req: express.Request, res: express.Response) {
45 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
76148b27
RK
46 if (!result) {
47 return res.fail({
48 status: HttpStatusCode.NOT_FOUND_404,
49 message: 'Torrent file not found'
50 })
51 }
90a8bd30 52
b6640fa0
C
53 const allowParameters = {
54 req,
55 res,
56 torrentPath: result.path,
57 downloadName: result.downloadName
58 }
4bc45da3
C
59
60 const allowedResult = await Hooks.wrapFun(
61 isTorrentDownloadAllowed,
62 allowParameters,
63 'filter:api.download.torrent.allowed.result'
64 )
65
66 if (!checkAllowResult(res, allowParameters, allowedResult)) return
67
90a8bd30
C
68 return res.download(result.path, result.downloadName)
69}
70
4bc45da3 71async function downloadVideoFile (req: express.Request, res: express.Response) {
90a8bd30
C
72 const video = res.locals.videoAll
73
74 const videoFile = getVideoFile(req, video.VideoFiles)
76148b27
RK
75 if (!videoFile) {
76 return res.fail({
77 status: HttpStatusCode.NOT_FOUND_404,
78 message: 'Video file not found'
79 })
80 }
90a8bd30 81
b6640fa0
C
82 const allowParameters = {
83 req,
84 res,
85 video,
86 videoFile
87 }
4bc45da3
C
88
89 const allowedResult = await Hooks.wrapFun(
90 isVideoDownloadAllowed,
91 allowParameters,
92 'filter:api.download.video.allowed.result'
93 )
94
95 if (!checkAllowResult(res, allowParameters, allowedResult)) return
96
0305db28 97 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
9ab330b9 98 return redirectToObjectStorage({ req, res, video, file: videoFile })
0305db28
JB
99 }
100
ad5db104 101 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), path => {
62f53731
C
102 // Express uses basename on filename parameter
103 const videoName = video.name.replace(/[/\\]/g, '_')
104 const filename = `${videoName}-${videoFile.resolution}p${videoFile.extname}`
0305db28
JB
105
106 return res.download(path, filename)
107 })
90a8bd30
C
108}
109
4bc45da3 110async function downloadHLSVideoFile (req: express.Request, res: express.Response) {
90a8bd30 111 const video = res.locals.videoAll
4bc45da3
C
112 const streamingPlaylist = getHLSPlaylist(video)
113 if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
90a8bd30 114
4bc45da3 115 const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
76148b27
RK
116 if (!videoFile) {
117 return res.fail({
118 status: HttpStatusCode.NOT_FOUND_404,
119 message: 'Video file not found'
120 })
121 }
90a8bd30 122
b6640fa0
C
123 const allowParameters = {
124 req,
125 res,
126 video,
127 streamingPlaylist,
128 videoFile
129 }
4bc45da3
C
130
131 const allowedResult = await Hooks.wrapFun(
132 isVideoDownloadAllowed,
133 allowParameters,
134 'filter:api.download.video.allowed.result'
135 )
136
137 if (!checkAllowResult(res, allowParameters, allowedResult)) return
138
0305db28 139 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
9ab330b9 140 return redirectToObjectStorage({ req, res, video, file: videoFile })
0305db28
JB
141 }
142
ad5db104 143 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(streamingPlaylist), path => {
0305db28
JB
144 const filename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}`
145
146 return res.download(path, filename)
147 })
90a8bd30
C
148}
149
150function getVideoFile (req: express.Request, files: MVideoFile[]) {
4638cd71 151 const resolution = forceNumber(req.params.resolution)
90a8bd30
C
152 return files.find(f => f.resolution === resolution)
153}
154
155function getHLSPlaylist (video: MVideoFullLight) {
156 const playlist = video.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
157 if (!playlist) return undefined
158
159 return Object.assign(playlist, { Video: video })
160}
4bc45da3
C
161
162type AllowedResult = {
163 allowed: boolean
164 errorMessage?: string
165}
166
167function isTorrentDownloadAllowed (_object: {
168 torrentPath: string
169}): AllowedResult {
170 return { allowed: true }
171}
172
173function isVideoDownloadAllowed (_object: {
174 video: MVideo
175 videoFile: MVideoFile
176 streamingPlaylist?: MStreamingPlaylist
177}): AllowedResult {
178 return { allowed: true }
179}
180
181function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
182 if (!result || result.allowed !== true) {
183 logger.info('Download is not allowed.', { result, allowParameters })
4bc45da3 184
76148b27
RK
185 res.fail({
186 status: HttpStatusCode.FORBIDDEN_403,
187 message: result?.errorMessage || 'Refused download'
188 })
4bc45da3
C
189 return false
190 }
191
192 return true
193}
9ab330b9
C
194
195function redirectToObjectStorage (options: {
196 req: express.Request
197 res: express.Response
198 video: MVideo
199 file: MVideoFile
200}) {
201 const { req, res, video, file } = options
202
203 const baseUrl = file.getObjectStorageUrl(video)
204
205 const url = video.hasPrivateStaticPath() && req.query.videoFileToken
206 ? addQueryParams(baseUrl, { videoFileToken: req.query.videoFileToken })
207 : baseUrl
208
209 return res.redirect(url)
210}