]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/core-utils.ts
allow public video privacy to be deleted in the web client
[github/Chocobozzz/PeerTube.git] / server / helpers / core-utils.ts
1 /* eslint-disable no-useless-call */
2
3 /*
4 Different from 'utils' because we don't import other PeerTube modules.
5 Useful to avoid circular dependencies.
6 */
7
8 import { exec, ExecOptions } from 'child_process'
9 import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto'
10 import { truncate } from 'lodash'
11 import { basename, isAbsolute, join, resolve } from 'path'
12 import * as pem from 'pem'
13 import { pipeline } from 'stream'
14 import { URL } from 'url'
15 import { promisify } from 'util'
16
17 const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
18 if (!oldObject || typeof oldObject !== 'object') {
19 return valueConverter(oldObject)
20 }
21
22 if (Array.isArray(oldObject)) {
23 return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
24 }
25
26 const newObject = {}
27 Object.keys(oldObject).forEach(oldKey => {
28 const newKey = keyConverter(oldKey)
29 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
30 })
31
32 return newObject
33 }
34
35 const timeTable = {
36 ms: 1,
37 second: 1000,
38 minute: 60000,
39 hour: 3600000,
40 day: 3600000 * 24,
41 week: 3600000 * 24 * 7,
42 month: 3600000 * 24 * 30
43 }
44
45 export function parseDurationToMs (duration: number | string): number {
46 if (duration === null) return null
47 if (typeof duration === 'number') return duration
48
49 if (typeof duration === 'string') {
50 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
51
52 if (split.length === 3) {
53 const len = parseFloat(split[1])
54 let unit = split[2].replace(/s$/i, '').toLowerCase()
55 if (unit === 'm') {
56 unit = 'ms'
57 }
58
59 return (len || 1) * (timeTable[unit] || 0)
60 }
61 }
62
63 throw new Error(`Duration ${duration} could not be properly parsed`)
64 }
65
66 export function parseBytes (value: string | number): number {
67 if (typeof value === 'number') return value
68
69 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
70 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
71 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
72 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
73 const t = /^(\d+)\s*TB$/
74 const g = /^(\d+)\s*GB$/
75 const m = /^(\d+)\s*MB$/
76 const b = /^(\d+)\s*B$/
77 let match
78
79 if (value.match(tgm)) {
80 match = value.match(tgm)
81 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
82 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
83 parseInt(match[3], 10) * 1024 * 1024
84 } else if (value.match(tg)) {
85 match = value.match(tg)
86 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
87 parseInt(match[2], 10) * 1024 * 1024 * 1024
88 } else if (value.match(tm)) {
89 match = value.match(tm)
90 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
91 parseInt(match[2], 10) * 1024 * 1024
92 } else if (value.match(gm)) {
93 match = value.match(gm)
94 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
95 parseInt(match[2], 10) * 1024 * 1024
96 } else if (value.match(t)) {
97 match = value.match(t)
98 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
99 } else if (value.match(g)) {
100 match = value.match(g)
101 return parseInt(match[1], 10) * 1024 * 1024 * 1024
102 } else if (value.match(m)) {
103 match = value.match(m)
104 return parseInt(match[1], 10) * 1024 * 1024
105 } else if (value.match(b)) {
106 match = value.match(b)
107 return parseInt(match[1], 10) * 1024
108 } else {
109 return parseInt(value, 10)
110 }
111 }
112
113 function sanitizeUrl (url: string) {
114 const urlObject = new URL(url)
115
116 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
117 urlObject.port = ''
118 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
119 urlObject.port = ''
120 }
121
122 return urlObject.href.replace(/\/$/, '')
123 }
124
125 // Don't import remote scheme from constants because we are in core utils
126 function sanitizeHost (host: string, remoteScheme: string) {
127 const toRemove = remoteScheme === 'https' ? 443 : 80
128
129 return host.replace(new RegExp(`:${toRemove}$`), '')
130 }
131
132 function isTestInstance () {
133 return process.env.NODE_ENV === 'test'
134 }
135
136 function isProdInstance () {
137 return process.env.NODE_ENV === 'production'
138 }
139
140 function getAppNumber () {
141 return process.env.NODE_APP_INSTANCE
142 }
143
144 let rootPath: string
145
146 function root () {
147 if (rootPath) return rootPath
148
149 // We are in /helpers/utils.js
150 rootPath = join(__dirname, '..', '..')
151
152 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
153
154 return rootPath
155 }
156
157 function pageToStartAndCount (page: number, itemsPerPage: number) {
158 const start = (page - 1) * itemsPerPage
159
160 return { start, count: itemsPerPage }
161 }
162
163 function mapToJSON (map: Map<any, any>) {
164 const obj: any = {}
165
166 for (const [ k, v ] of map) {
167 obj[k] = v
168 }
169
170 return obj
171 }
172
173 function buildPath (path: string) {
174 if (isAbsolute(path)) return path
175
176 return join(root(), path)
177 }
178
179 // Consistent with .length, lodash truncate function is not
180 function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
181 const truncatedStr = truncate(str, options)
182
183 // The truncated string is okay, we can return it
184 if (truncatedStr.length <= options.length) return truncatedStr
185
186 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
187 // We always use the .length so we need to truncate more if needed
188 options.length -= truncatedStr.length - options.length
189 return truncate(str, options)
190 }
191
192 function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
193 return createHash('sha256').update(str).digest(encoding)
194 }
195
196 function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
197 return createHash('sha1').update(str).digest(encoding)
198 }
199
200 function execShell (command: string, options?: ExecOptions) {
201 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
202 exec(command, options, (err, stdout, stderr) => {
203 // eslint-disable-next-line prefer-promise-reject-errors
204 if (err) return rej({ err, stdout, stderr })
205
206 return res({ stdout, stderr })
207 })
208 })
209 }
210
211 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
212 return function promisified (): Promise<A> {
213 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
214 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
215 })
216 }
217 }
218
219 // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
220 function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
221 return function promisified (arg: T): Promise<A> {
222 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
223 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
224 })
225 }
226 }
227
228 function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
229 return function promisified (arg1: T, arg2: U): Promise<A> {
230 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
231 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
232 })
233 }
234 }
235
236 type SemVersion = { major: number, minor: number, patch: number }
237 function 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
247 const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
248 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
249 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
250 const execPromise2 = promisify2<string, any, string>(exec)
251 const execPromise = promisify1<string, string>(exec)
252 const pipelinePromise = promisify(pipeline)
253
254 // ---------------------------------------------------------------------------
255
256 export {
257 isTestInstance,
258 isProdInstance,
259 getAppNumber,
260
261 objectConverter,
262 root,
263 pageToStartAndCount,
264 sanitizeUrl,
265 sanitizeHost,
266 buildPath,
267 execShell,
268 peertubeTruncate,
269
270 sha256,
271 sha1,
272 mapToJSON,
273
274 promisify0,
275 promisify1,
276 promisify2,
277
278 randomBytesPromise,
279 createPrivateKey,
280 getPublicKey,
281 execPromise2,
282 execPromise,
283 pipelinePromise,
284
285 parseSemVersion
286 }