diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a44e9a7..16a74f315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v4.1.0-rc.1 +## v4.1.0-rc.1 (unreleased) ### IMPORTANT NOTES diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html index c9533208a..37989cb59 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html @@ -267,14 +267,22 @@ + > + + ⚠️ If enabled, we recommend to use a HTTP proxy to prevent private URL access from your PeerTube server + +
+ > + + ⚠️ We don't recommend to enable this feature if you don't trust your users + +
diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts index 1825ce278..718493dc7 100644 --- a/client/src/app/+admin/follows/follows.routes.ts +++ b/client/src/app/+admin/follows/follows.routes.ts @@ -42,7 +42,12 @@ export const FollowsRoutes: Routes = [ }, { path: 'video-redundancies-list', - component: VideoRedundanciesListComponent + component: VideoRedundanciesListComponent, + data: { + meta: { + title: $localize`Redundancy` + } + } } ] } diff --git a/config/default.yaml b/config/default.yaml index 2b0419535..23be08f85 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -431,7 +431,10 @@ import: # Amount of import jobs to execute in parallel concurrency: 1 - http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html + # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html + http: + # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server + # See https://docs.joinpeertube.org/maintain-configuration?id=security for more information enabled: false youtube_dl_release: @@ -452,7 +455,10 @@ import: # IPv6 is very strongly rate-limited on most sites supported by youtube-dl force_ipv4: false - torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file) + # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file) + torrent: + # We recommend to only enable magnet URI/torrent import if you trust your users + # See https://docs.joinpeertube.org/maintain-configuration?id=security for more information enabled: false auto_blacklist: diff --git a/config/production.yaml.example b/config/production.yaml.example index 893ccc33e..675801caa 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -439,7 +439,10 @@ import: # Amount of import jobs to execute in parallel concurrency: 1 - http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html + # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html + http: + # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server + # See https://docs.joinpeertube.org/maintain-configuration?id=security for more information enabled: false youtube_dl_release: @@ -460,7 +463,10 @@ import: # IPv6 is very strongly rate-limited on most sites supported by youtube-dl force_ipv4: false - torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file) + # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file) + torrent: + # We recommend to only enable magnet URI/torrent import if you trust your users + # See https://docs.joinpeertube.org/maintain-configuration?id=security for more information enabled: false auto_blacklist: diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 8cbfd3286..b54fa822c 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts @@ -1,9 +1,11 @@ import express from 'express' -import { move, readFile } from 'fs-extra' +import { move, readFile, remove } from 'fs-extra' import { decode } from 'magnet-uri' import parseTorrent, { Instance } from 'parse-torrent' import { join } from 'path' +import { isVTTFileValid } from '@server/helpers/custom-validators/video-captions' import { isVideoFileExtnameValid } from '@server/helpers/custom-validators/videos' +import { isResolvingToUnicastOnly } from '@server/helpers/dns' import { Hooks } from '@server/lib/plugins/hooks' import { ServerConfigManager } from '@server/lib/server-config-manager' import { setVideoTags } from '@server/lib/video' @@ -195,6 +197,13 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) }) } + if (!await hasUnicastURLsOnly(youtubeDLInfo)) { + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot use non unicast IP as targetUrl.' + }) + } + const video = await buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) // Process video thumbnail from request.files @@ -432,6 +441,11 @@ async function processYoutubeSubtitles (youtubeDL: YoutubeDLWrapper, targetUrl: logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl) for (const subtitle of subtitles) { + if (!await isVTTFileValid(subtitle.path)) { + await remove(subtitle.path) + continue + } + const videoCaption = new VideoCaptionModel({ videoId, language: subtitle.language, @@ -449,3 +463,16 @@ async function processYoutubeSubtitles (youtubeDL: YoutubeDLWrapper, targetUrl: logger.warn('Cannot get video subtitles.', { err }) } } + +async function hasUnicastURLsOnly (youtubeDLInfo: YoutubeDLInfo) { + const hosts = youtubeDLInfo.urls.map(u => new URL(u).hostname) + const uniqHosts = new Set(hosts) + + for (const h of uniqHosts) { + if (await isResolvingToUnicastOnly(h) !== true) { + return false + } + } + + return true +} diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index 528edf60c..4cc7dcaf4 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts @@ -1,3 +1,5 @@ +import { getFileSize } from '@shared/extra-utils' +import { readFile } from 'fs-extra' import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers/constants' import { exists, isFileValid } from './misc' @@ -13,9 +15,20 @@ function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) } +async function isVTTFileValid (filePath: string) { + const size = await getFileSize(filePath) + + if (size > CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) return false + + const content = await readFile(filePath, 'utf8') + + return content?.startsWith('WEBVTT\n') +} + // --------------------------------------------------------------------------- export { isVideoCaptionFile, + isVTTFileValid, isVideoCaptionLanguageValid } diff --git a/server/helpers/youtube-dl/youtube-dl-info-builder.ts b/server/helpers/youtube-dl/youtube-dl-info-builder.ts index 9746a7067..71572f292 100644 --- a/server/helpers/youtube-dl/youtube-dl-info-builder.ts +++ b/server/helpers/youtube-dl/youtube-dl-info-builder.ts @@ -1,5 +1,6 @@ import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants' import { peertubeTruncate } from '../core-utils' +import { isUrlValid } from '../custom-validators/activitypub/misc' type YoutubeDLInfo = { name?: string @@ -12,6 +13,8 @@ type YoutubeDLInfo = { thumbnailUrl?: string ext?: string originallyPublishedAt?: Date + + urls?: string[] } class YoutubeDLInfoBuilder { @@ -76,11 +79,57 @@ class YoutubeDLInfoBuilder { nsfw: this.isNSFW(obj), tags: this.getTags(obj.tags), thumbnailUrl: obj.thumbnail || undefined, + urls: this.buildAvailableUrl(obj), originallyPublishedAt: this.buildOriginallyPublishedAt(obj), ext: obj.ext } } + private buildAvailableUrl (obj: any) { + const urls: string[] = [] + + if (obj.url) urls.push(obj.url) + if (obj.urls) { + if (Array.isArray(obj.urls)) urls.push(...obj.urls) + else urls.push(obj.urls) + } + + const formats = Array.isArray(obj.formats) + ? obj.formats + : [] + + for (const format of formats) { + if (!format.url) continue + + urls.push(format.url) + } + + const thumbnails = Array.isArray(obj.thumbnails) + ? obj.thumbnails + : [] + + for (const thumbnail of thumbnails) { + if (!thumbnail.url) continue + + urls.push(thumbnail.url) + } + + if (obj.thumbnail) urls.push(obj.thumbnail) + + for (const subtitleKey of Object.keys(obj.subtitles || {})) { + const subtitles = obj.subtitles[subtitleKey] + if (!Array.isArray(subtitles)) continue + + for (const subtitle of subtitles) { + if (!subtitle.url) continue + + urls.push(subtitle.url) + } + } + + return urls.filter(u => u && isUrlValid(u)) + } + private titleTruncation (title: string) { return peertubeTruncate(title, { length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max,