Merge branch 'release/beta-10' into develop
[oweals/peertube.git] / server / helpers / utils.ts
1 import { Model } from 'sequelize-typescript'
2 import * as ipaddr from 'ipaddr.js'
3 import { ResultList } from '../../shared'
4 import { VideoResolution } from '../../shared/models/videos'
5 import { CONFIG } from '../initializers'
6 import { UserModel } from '../models/account/user'
7 import { ActorModel } from '../models/activitypub/actor'
8 import { ApplicationModel } from '../models/application/application'
9 import { pseudoRandomBytesPromise, unlinkPromise } from './core-utils'
10 import { logger } from './logger'
11 import { isArray } from './custom-validators/misc'
12
13 const isCidr = require('is-cidr')
14
15 function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[] }) {
16   const files = req.files
17
18   if (!files) return
19
20   if (isArray(files)) {
21     (files as Express.Multer.File[]).forEach(f => deleteFileAsync(f.path))
22     return
23   }
24
25   for (const key of Object.keys(files)) {
26     const file = files[key]
27
28     if (isArray(file)) file.forEach(f => deleteFileAsync(f.path))
29     else deleteFileAsync(file.path)
30   }
31 }
32
33 function deleteFileAsync (path: string) {
34   unlinkPromise(path)
35     .catch(err => logger.error('Cannot delete the file %s asynchronously.', path, { err }))
36 }
37
38 async function generateRandomString (size: number) {
39   const raw = await pseudoRandomBytesPromise(size)
40
41   return raw.toString('hex')
42 }
43
44 interface FormattableToJSON {
45   toFormattedJSON (args?: any)
46 }
47
48 function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number, formattedArg?: any) {
49   const formattedObjects: U[] = []
50
51   objects.forEach(object => {
52     formattedObjects.push(object.toFormattedJSON(formattedArg))
53   })
54
55   return {
56     total: objectsTotal,
57     data: formattedObjects
58   } as ResultList<U>
59 }
60
61 async function isSignupAllowed () {
62   if (CONFIG.SIGNUP.ENABLED === false) {
63     return false
64   }
65
66   // No limit and signup is enabled
67   if (CONFIG.SIGNUP.LIMIT === -1) {
68     return true
69   }
70
71   const totalUsers = await UserModel.countTotal()
72
73   return totalUsers < CONFIG.SIGNUP.LIMIT
74 }
75
76 function isSignupAllowedForCurrentIP (ip: string) {
77   const addr = ipaddr.parse(ip)
78   let excludeList = [ 'blacklist' ]
79   let matched = ''
80
81   // if there is a valid, non-empty whitelist, we exclude all unknown adresses too
82   if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) {
83     excludeList.push('unknown')
84   }
85
86   if (addr.kind() === 'ipv4') {
87     const addrV4 = ipaddr.IPv4.parse(ip)
88     const rangeList = {
89       whitelist: CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr.v4(cidr))
90                                                 .map(cidr => ipaddr.IPv4.parseCIDR(cidr)),
91       blacklist: CONFIG.SIGNUP.FILTERS.CIDR.BLACKLIST.filter(cidr => isCidr.v4(cidr))
92                                                 .map(cidr => ipaddr.IPv4.parseCIDR(cidr))
93     }
94     matched = ipaddr.subnetMatch(addrV4, rangeList, 'unknown')
95   } else if (addr.kind() === 'ipv6') {
96     const addrV6 = ipaddr.IPv6.parse(ip)
97     const rangeList = {
98       whitelist: CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr.v6(cidr))
99                                                 .map(cidr => ipaddr.IPv6.parseCIDR(cidr)),
100       blacklist: CONFIG.SIGNUP.FILTERS.CIDR.BLACKLIST.filter(cidr => isCidr.v6(cidr))
101                                                 .map(cidr => ipaddr.IPv6.parseCIDR(cidr))
102     }
103     matched = ipaddr.subnetMatch(addrV6, rangeList, 'unknown')
104   }
105
106   return !excludeList.includes(matched)
107 }
108
109 function computeResolutionsToTranscode (videoFileHeight: number) {
110   const resolutionsEnabled: number[] = []
111   const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
112
113   // Put in the order we want to proceed jobs
114   const resolutions = [
115     VideoResolution.H_480P,
116     VideoResolution.H_360P,
117     VideoResolution.H_720P,
118     VideoResolution.H_240P,
119     VideoResolution.H_1080P
120   ]
121
122   for (const resolution of resolutions) {
123     if (configResolutions[ resolution + 'p' ] === true && videoFileHeight > resolution) {
124       resolutionsEnabled.push(resolution)
125     }
126   }
127
128   return resolutionsEnabled
129 }
130
131 const timeTable = {
132   ms:           1,
133   second:       1000,
134   minute:       60000,
135   hour:         3600000,
136   day:          3600000 * 24,
137   week:         3600000 * 24 * 7,
138   month:        3600000 * 24 * 30
139 }
140 export function parseDuration (duration: number | string): number {
141   if (typeof duration === 'number') return duration
142
143   if (typeof duration === 'string') {
144     const split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
145
146     if (split.length === 3) {
147       const len = parseFloat(split[1])
148       let unit = split[2].replace(/s$/i,'').toLowerCase()
149       if (unit === 'm') {
150         unit = 'ms'
151       }
152
153       return (len || 1) * (timeTable[unit] || 0)
154     }
155   }
156
157   throw new Error('Duration could not be properly parsed')
158 }
159
160 function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
161   Object.keys(savedFields).forEach(key => {
162     const value = savedFields[key]
163     instance.set(key, value)
164   })
165 }
166
167 let serverActor: ActorModel
168 async function getServerActor () {
169   if (serverActor === undefined) {
170     const application = await ApplicationModel.load()
171     if (!application) throw Error('Could not load Application from database.')
172
173     serverActor = application.Account.Actor
174   }
175
176   if (!serverActor) {
177     logger.error('Cannot load server actor.')
178     process.exit(0)
179   }
180
181   return Promise.resolve(serverActor)
182 }
183
184 type SortType = { sortModel: any, sortValue: string }
185
186 // ---------------------------------------------------------------------------
187
188 export {
189   cleanUpReqFiles,
190   deleteFileAsync,
191   generateRandomString,
192   getFormattedObjects,
193   isSignupAllowed,
194   isSignupAllowedForCurrentIP,
195   computeResolutionsToTranscode,
196   resetSequelizeInstance,
197   getServerActor,
198   SortType
199 }