aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-06-08 09:33:03 +0200
committerChocobozzz <me@florianbigard.com>2021-06-08 10:01:50 +0200
commitea54cd04c1ff0e55651cd5fb1a83672acde68604 (patch)
tree4e10fea658e72a9f79375d0e9b23d08915d3d0b2 /server
parent295106516277581ba4967199fa5580264a90ae2c (diff)
downloadPeerTube-ea54cd04c1ff0e55651cd5fb1a83672acde68604.tar.gz
PeerTube-ea54cd04c1ff0e55651cd5fb1a83672acde68604.tar.zst
PeerTube-ea54cd04c1ff0e55651cd5fb1a83672acde68604.zip
Fix video upload with a capitalized ext
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/upload.ts4
-rw-r--r--server/helpers/core-utils.ts88
-rw-r--r--server/helpers/express-utils.ts4
-rw-r--r--server/helpers/image-utils.ts7
-rw-r--r--server/lib/activitypub/actors/shared/object-to-model-attributes.ts4
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts3
-rw-r--r--server/lib/job-queue/handlers/video-import.ts3
-rw-r--r--server/lib/local-actor.ts5
-rw-r--r--server/models/actor/actor.ts6
9 files changed, 78 insertions, 46 deletions
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index 783cc329a..e767492bc 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { move } from 'fs-extra' 2import { move } from 'fs-extra'
3import { extname } from 'path' 3import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload' 4import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload'
5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
@@ -225,7 +225,7 @@ async function addVideo (options: {
225 225
226async function buildNewFile (video: MVideo, videoPhysicalFile: express.VideoUploadFile) { 226async function buildNewFile (video: MVideo, videoPhysicalFile: express.VideoUploadFile) {
227 const videoFile = new VideoFileModel({ 227 const videoFile = new VideoFileModel({
228 extname: extname(videoPhysicalFile.filename), 228 extname: getLowercaseExtension(videoPhysicalFile.filename),
229 size: videoPhysicalFile.size, 229 size: videoPhysicalFile.size,
230 videoStreamingPlaylistId: null, 230 videoStreamingPlaylistId: null,
231 metadata: await getMetadataFromFile(videoPhysicalFile.path) 231 metadata: await getMetadataFromFile(videoPhysicalFile.path)
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index b93868c12..b5bf2c92c 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -8,7 +8,7 @@
8import { exec, ExecOptions } from 'child_process' 8import { exec, ExecOptions } from 'child_process'
9import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto' 9import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto'
10import { truncate } from 'lodash' 10import { truncate } from 'lodash'
11import { basename, isAbsolute, join, resolve } from 'path' 11import { basename, extname, isAbsolute, join, resolve } from 'path'
12import * as pem from 'pem' 12import * as pem from 'pem'
13import { pipeline } from 'stream' 13import { pipeline } from 'stream'
14import { URL } from 'url' 14import { URL } from 'url'
@@ -32,6 +32,18 @@ const objectConverter = (oldObject: any, keyConverter: (e: string) => string, va
32 return newObject 32 return newObject
33} 33}
34 34
35function mapToJSON (map: Map<any, any>) {
36 const obj: any = {}
37
38 for (const [ k, v ] of map) {
39 obj[k] = v
40 }
41
42 return obj
43}
44
45// ---------------------------------------------------------------------------
46
35const timeTable = { 47const timeTable = {
36 ms: 1, 48 ms: 1,
37 second: 1000, 49 second: 1000,
@@ -110,6 +122,8 @@ export function parseBytes (value: string | number): number {
110 } 122 }
111} 123}
112 124
125// ---------------------------------------------------------------------------
126
113function sanitizeUrl (url: string) { 127function sanitizeUrl (url: string) {
114 const urlObject = new URL(url) 128 const urlObject = new URL(url)
115 129
@@ -129,6 +143,8 @@ function sanitizeHost (host: string, remoteScheme: string) {
129 return host.replace(new RegExp(`:${toRemove}$`), '') 143 return host.replace(new RegExp(`:${toRemove}$`), '')
130} 144}
131 145
146// ---------------------------------------------------------------------------
147
132function isTestInstance () { 148function isTestInstance () {
133 return process.env.NODE_ENV === 'test' 149 return process.env.NODE_ENV === 'test'
134} 150}
@@ -141,6 +157,8 @@ function getAppNumber () {
141 return process.env.NODE_APP_INSTANCE 157 return process.env.NODE_APP_INSTANCE
142} 158}
143 159
160// ---------------------------------------------------------------------------
161
144let rootPath: string 162let rootPath: string
145 163
146function root () { 164function root () {
@@ -154,27 +172,19 @@ function root () {
154 return rootPath 172 return rootPath
155} 173}
156 174
157function pageToStartAndCount (page: number, itemsPerPage: number) { 175function buildPath (path: string) {
158 const start = (page - 1) * itemsPerPage 176 if (isAbsolute(path)) return path
159 177
160 return { start, count: itemsPerPage } 178 return join(root(), path)
161} 179}
162 180
163function mapToJSON (map: Map<any, any>) { 181function getLowercaseExtension (filename: string) {
164 const obj: any = {} 182 const ext = extname(filename) || ''
165
166 for (const [ k, v ] of map) {
167 obj[k] = v
168 }
169 183
170 return obj 184 return ext.toLowerCase()
171} 185}
172 186
173function buildPath (path: string) { 187// ---------------------------------------------------------------------------
174 if (isAbsolute(path)) return path
175
176 return join(root(), path)
177}
178 188
179// Consistent with .length, lodash truncate function is not 189// Consistent with .length, lodash truncate function is not
180function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) { 190function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
@@ -189,6 +199,27 @@ function peertubeTruncate (str: string, options: { length: number, separator?: R
189 return truncate(str, options) 199 return truncate(str, options)
190} 200}
191 201
202function pageToStartAndCount (page: number, itemsPerPage: number) {
203 const start = (page - 1) * itemsPerPage
204
205 return { start, count: itemsPerPage }
206}
207
208// ---------------------------------------------------------------------------
209
210type SemVersion = { major: number, minor: number, patch: number }
211function parseSemVersion (s: string) {
212 const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
213
214 return {
215 major: parseInt(parsed[1]),
216 minor: parseInt(parsed[2]),
217 patch: parseInt(parsed[3])
218 } as SemVersion
219}
220
221// ---------------------------------------------------------------------------
222
192function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') { 223function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
193 return createHash('sha256').update(str).digest(encoding) 224 return createHash('sha256').update(str).digest(encoding)
194} 225}
@@ -197,6 +228,8 @@ function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
197 return createHash('sha1').update(str).digest(encoding) 228 return createHash('sha1').update(str).digest(encoding)
198} 229}
199 230
231// ---------------------------------------------------------------------------
232
200function execShell (command: string, options?: ExecOptions) { 233function execShell (command: string, options?: ExecOptions) {
201 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { 234 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
202 exec(command, options, (err, stdout, stderr) => { 235 exec(command, options, (err, stdout, stderr) => {
@@ -208,6 +241,8 @@ function execShell (command: string, options?: ExecOptions) {
208 }) 241 })
209} 242}
210 243
244// ---------------------------------------------------------------------------
245
211function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { 246function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
212 return function promisified (): Promise<A> { 247 return function promisified (): Promise<A> {
213 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { 248 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
@@ -233,17 +268,6 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
233 } 268 }
234} 269}
235 270
236type SemVersion = { major: number, minor: number, patch: number }
237function parseSemVersion (s: string) {
238 const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
239
240 return {
241 major: parseInt(parsed[1]),
242 minor: parseInt(parsed[2]),
243 patch: parseInt(parsed[3])
244 } as SemVersion
245}
246
247const randomBytesPromise = promisify1<number, Buffer>(randomBytes) 271const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
248const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) 272const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
249const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) 273const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
@@ -259,17 +283,21 @@ export {
259 getAppNumber, 283 getAppNumber,
260 284
261 objectConverter, 285 objectConverter,
286 mapToJSON,
287
262 root, 288 root,
263 pageToStartAndCount, 289 buildPath,
290 getLowercaseExtension,
264 sanitizeUrl, 291 sanitizeUrl,
265 sanitizeHost, 292 sanitizeHost,
266 buildPath, 293
267 execShell, 294 execShell,
295
296 pageToStartAndCount,
268 peertubeTruncate, 297 peertubeTruncate,
269 298
270 sha256, 299 sha256,
271 sha1, 300 sha1,
272 mapToJSON,
273 301
274 promisify0, 302 promisify0,
275 promisify1, 303 promisify1,
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 010c6961a..003e02818 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -1,9 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as multer from 'multer' 2import * as multer from 'multer'
3import { extname } from 'path'
4import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
5import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
6import { REMOTE_SCHEME } from '../initializers/constants' 5import { REMOTE_SCHEME } from '../initializers/constants'
6import { getLowercaseExtension } from './core-utils'
7import { isArray } from './custom-validators/misc' 7import { isArray } from './custom-validators/misc'
8import { logger } from './logger' 8import { logger } from './logger'
9import { deleteFileAndCatch, generateRandomString } from './utils' 9import { deleteFileAndCatch, generateRandomString } from './utils'
@@ -79,7 +79,7 @@ function createReqFiles (
79 79
80 filename: async (req, file, cb) => { 80 filename: async (req, file, cb) => {
81 let extension: string 81 let extension: string
82 const fileExtension = extname(file.originalname) 82 const fileExtension = getLowercaseExtension(file.originalname)
83 const extensionFromMimetype = getExtFromMimetype(mimeTypes, file.mimetype) 83 const extensionFromMimetype = getExtFromMimetype(mimeTypes, file.mimetype)
84 84
85 // Take the file extension if we don't understand the mime type 85 // Take the file extension if we don't understand the mime type
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
index 6f6f8d4da..122fb009d 100644
--- a/server/helpers/image-utils.ts
+++ b/server/helpers/image-utils.ts
@@ -1,7 +1,7 @@
1import { copy, readFile, remove, rename } from 'fs-extra' 1import { copy, readFile, remove, rename } from 'fs-extra'
2import * as Jimp from 'jimp' 2import * as Jimp from 'jimp'
3import { extname } from 'path'
4import { v4 as uuidv4 } from 'uuid' 3import { v4 as uuidv4 } from 'uuid'
4import { getLowercaseExtension } from './core-utils'
5import { convertWebPToJPG, processGIF } from './ffmpeg-utils' 5import { convertWebPToJPG, processGIF } from './ffmpeg-utils'
6import { logger } from './logger' 6import { logger } from './logger'
7 7
@@ -15,7 +15,7 @@ async function processImage (
15 newSize: { width: number, height: number }, 15 newSize: { width: number, height: number },
16 keepOriginal = false 16 keepOriginal = false
17) { 17) {
18 const extension = extname(path) 18 const extension = getLowercaseExtension(path)
19 19
20 if (path === destination) { 20 if (path === destination) {
21 throw new Error('Jimp/FFmpeg needs an input path different that the output path.') 21 throw new Error('Jimp/FFmpeg needs an input path different that the output path.')
@@ -61,7 +61,8 @@ async function jimpProcessor (path: string, destination: string, newSize: { widt
61 await remove(destination) 61 await remove(destination)
62 62
63 // Optimization if the source file has the appropriate size 63 // Optimization if the source file has the appropriate size
64 if (await skipProcessing({ jimpInstance, newSize, imageBytes: inputBuffer.byteLength, inputExt, outputExt: extname(destination) })) { 64 const outputExt = getLowercaseExtension(destination)
65 if (skipProcessing({ jimpInstance, newSize, imageBytes: inputBuffer.byteLength, inputExt, outputExt })) {
65 return copy(path, destination) 66 return copy(path, destination)
66 } 67 }
67 68
diff --git a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
index 66b22c952..f53b98448 100644
--- a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
@@ -1,5 +1,5 @@
1import { extname } from 'path'
2import { v4 as uuidv4 } from 'uuid' 1import { v4 as uuidv4 } from 'uuid'
2import { getLowercaseExtension } from '@server/helpers/core-utils'
3import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc' 3import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
4import { MIMETYPES } from '@server/initializers/constants' 4import { MIMETYPES } from '@server/initializers/constants'
5import { ActorModel } from '@server/models/actor/actor' 5import { ActorModel } from '@server/models/actor/actor'
@@ -43,7 +43,7 @@ function getImageInfoFromObject (actorObject: ActivityPubActor, type: ActorImage
43 if (icon.mediaType) { 43 if (icon.mediaType) {
44 extension = mimetypes.MIMETYPE_EXT[icon.mediaType] 44 extension = mimetypes.MIMETYPE_EXT[icon.mediaType]
45 } else { 45 } else {
46 const tmp = extname(icon.url) 46 const tmp = getLowercaseExtension(icon.url)
47 47
48 if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp 48 if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp
49 } 49 }
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 8297a1571..048963033 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -1,6 +1,7 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { copy, stat } from 'fs-extra' 2import { copy, stat } from 'fs-extra'
3import { extname } from 'path' 3import { extname } from 'path'
4import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 6import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
6import { UserModel } from '@server/models/user/user' 7import { UserModel } from '@server/models/user/user'
@@ -55,7 +56,7 @@ async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) {
55 const { size } = await stat(inputFilePath) 56 const { size } = await stat(inputFilePath)
56 const fps = await getVideoFileFPS(inputFilePath) 57 const fps = await getVideoFileFPS(inputFilePath)
57 58
58 const fileExt = extname(inputFilePath) 59 const fileExt = getLowercaseExtension(inputFilePath)
59 60
60 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === videoFileResolution) 61 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === videoFileResolution)
61 62
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index d71053e87..937f586c9 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -1,6 +1,7 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { move, remove, stat } from 'fs-extra' 2import { move, remove, stat } from 'fs-extra'
3import { extname } from 'path' 3import { extname } from 'path'
4import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { retryTransactionWrapper } from '@server/helpers/database-utils' 5import { retryTransactionWrapper } from '@server/helpers/database-utils'
5import { YoutubeDL } from '@server/helpers/youtube-dl' 6import { YoutubeDL } from '@server/helpers/youtube-dl'
6import { isPostImportVideoAccepted } from '@server/lib/moderation' 7import { isPostImportVideoAccepted } from '@server/lib/moderation'
@@ -119,7 +120,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
119 const duration = await getDurationFromVideoFile(tempVideoPath) 120 const duration = await getDurationFromVideoFile(tempVideoPath)
120 121
121 // Prepare video file object for creation in database 122 // Prepare video file object for creation in database
122 const fileExt = extname(tempVideoPath) 123 const fileExt = getLowercaseExtension(tempVideoPath)
123 const videoFileData = { 124 const videoFileData = {
124 extname: fileExt, 125 extname: fileExt,
125 resolution: videoFileResolution, 126 resolution: videoFileResolution,
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts
index 55e77dd04..2d2bd43a1 100644
--- a/server/lib/local-actor.ts
+++ b/server/lib/local-actor.ts
@@ -1,8 +1,9 @@
1import 'multer' 1import 'multer'
2import { queue } from 'async' 2import { queue } from 'async'
3import * as LRUCache from 'lru-cache' 3import * as LRUCache from 'lru-cache'
4import { extname, join } from 'path' 4import { join } from 'path'
5import { v4 as uuidv4 } from 'uuid' 5import { v4 as uuidv4 } from 'uuid'
6import { getLowercaseExtension } from '@server/helpers/core-utils'
6import { ActorModel } from '@server/models/actor/actor' 7import { ActorModel } from '@server/models/actor/actor'
7import { ActivityPubActorType, ActorImageType } from '@shared/models' 8import { ActivityPubActorType, ActorImageType } from '@shared/models'
8import { retryTransactionWrapper } from '../helpers/database-utils' 9import { retryTransactionWrapper } from '../helpers/database-utils'
@@ -41,7 +42,7 @@ async function updateLocalActorImageFile (
41 ? ACTOR_IMAGES_SIZE.AVATARS 42 ? ACTOR_IMAGES_SIZE.AVATARS
42 : ACTOR_IMAGES_SIZE.BANNERS 43 : ACTOR_IMAGES_SIZE.BANNERS
43 44
44 const extension = extname(imagePhysicalFile.filename) 45 const extension = getLowercaseExtension(imagePhysicalFile.filename)
45 46
46 const imageName = uuidv4() + extension 47 const imageName = uuidv4() + extension
47 const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName) 48 const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName)
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts
index 65c53f8f8..0cd30f545 100644
--- a/server/models/actor/actor.ts
+++ b/server/models/actor/actor.ts
@@ -1,5 +1,4 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import { extname } from 'path'
3import { literal, Op, Transaction } from 'sequelize' 2import { literal, Op, Transaction } from 'sequelize'
4import { 3import {
5 AllowNull, 4 AllowNull,
@@ -17,6 +16,7 @@ import {
17 Table, 16 Table,
18 UpdatedAt 17 UpdatedAt
19} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { getLowercaseExtension } from '@server/helpers/core-utils'
20import { ModelCache } from '@server/models/model-cache' 20import { ModelCache } from '@server/models/model-cache'
21import { AttributesOnly } from '@shared/core-utils' 21import { AttributesOnly } from '@shared/core-utils'
22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' 22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
@@ -567,7 +567,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
567 let image: ActivityIconObject 567 let image: ActivityIconObject
568 568
569 if (this.avatarId) { 569 if (this.avatarId) {
570 const extension = extname(this.Avatar.filename) 570 const extension = getLowercaseExtension(this.Avatar.filename)
571 571
572 icon = { 572 icon = {
573 type: 'Image', 573 type: 'Image',
@@ -580,7 +580,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
580 580
581 if (this.bannerId) { 581 if (this.bannerId) {
582 const banner = (this as MActorAPChannel).Banner 582 const banner = (this as MActorAPChannel).Banner
583 const extension = extname(banner.filename) 583 const extension = getLowercaseExtension(banner.filename)
584 584
585 image = { 585 image = {
586 type: 'Image', 586 type: 'Image',