diff options
Diffstat (limited to 'server/helpers/core-utils.ts')
-rw-r--r-- | server/helpers/core-utils.ts | 315 |
1 files changed, 0 insertions, 315 deletions
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts deleted file mode 100644 index 242c49e89..000000000 --- a/server/helpers/core-utils.ts +++ /dev/null | |||
@@ -1,315 +0,0 @@ | |||
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 { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions, scrypt } from 'crypto' | ||
10 | import { truncate } from 'lodash' | ||
11 | import { pipeline } from 'stream' | ||
12 | import { URL } from 'url' | ||
13 | import { promisify } from 'util' | ||
14 | import { promisify1, promisify2, promisify3 } from '@shared/core-utils' | ||
15 | |||
16 | const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { | ||
17 | if (!oldObject || typeof oldObject !== 'object') { | ||
18 | return valueConverter(oldObject) | ||
19 | } | ||
20 | |||
21 | if (Array.isArray(oldObject)) { | ||
22 | return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) | ||
23 | } | ||
24 | |||
25 | const newObject = {} | ||
26 | Object.keys(oldObject).forEach(oldKey => { | ||
27 | const newKey = keyConverter(oldKey) | ||
28 | newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter) | ||
29 | }) | ||
30 | |||
31 | return newObject | ||
32 | } | ||
33 | |||
34 | function mapToJSON (map: Map<any, any>) { | ||
35 | const obj: any = {} | ||
36 | |||
37 | for (const [ k, v ] of map) { | ||
38 | obj[k] = v | ||
39 | } | ||
40 | |||
41 | return obj | ||
42 | } | ||
43 | |||
44 | // --------------------------------------------------------------------------- | ||
45 | |||
46 | const timeTable = { | ||
47 | ms: 1, | ||
48 | second: 1000, | ||
49 | minute: 60000, | ||
50 | hour: 3600000, | ||
51 | day: 3600000 * 24, | ||
52 | week: 3600000 * 24 * 7, | ||
53 | month: 3600000 * 24 * 30 | ||
54 | } | ||
55 | |||
56 | export function parseDurationToMs (duration: number | string): number { | ||
57 | if (duration === null) return null | ||
58 | if (typeof duration === 'number') return duration | ||
59 | if (!isNaN(+duration)) return +duration | ||
60 | |||
61 | if (typeof duration === 'string') { | ||
62 | const split = duration.match(/^([\d.,]+)\s?(\w+)$/) | ||
63 | |||
64 | if (split.length === 3) { | ||
65 | const len = parseFloat(split[1]) | ||
66 | let unit = split[2].replace(/s$/i, '').toLowerCase() | ||
67 | if (unit === 'm') { | ||
68 | unit = 'ms' | ||
69 | } | ||
70 | |||
71 | return (len || 1) * (timeTable[unit] || 0) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | throw new Error(`Duration ${duration} could not be properly parsed`) | ||
76 | } | ||
77 | |||
78 | export function parseBytes (value: string | number): number { | ||
79 | if (typeof value === 'number') return value | ||
80 | if (!isNaN(+value)) return +value | ||
81 | |||
82 | const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/ | ||
83 | const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/ | ||
84 | const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/ | ||
85 | const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/ | ||
86 | const t = /^(\d+)\s*TB$/ | ||
87 | const g = /^(\d+)\s*GB$/ | ||
88 | const m = /^(\d+)\s*MB$/ | ||
89 | const b = /^(\d+)\s*B$/ | ||
90 | |||
91 | let match: RegExpMatchArray | ||
92 | |||
93 | if (value.match(tgm)) { | ||
94 | match = value.match(tgm) | ||
95 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | ||
96 | parseInt(match[2], 10) * 1024 * 1024 * 1024 + | ||
97 | parseInt(match[3], 10) * 1024 * 1024 | ||
98 | } | ||
99 | |||
100 | if (value.match(tg)) { | ||
101 | match = value.match(tg) | ||
102 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | ||
103 | parseInt(match[2], 10) * 1024 * 1024 * 1024 | ||
104 | } | ||
105 | |||
106 | if (value.match(tm)) { | ||
107 | match = value.match(tm) | ||
108 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | ||
109 | parseInt(match[2], 10) * 1024 * 1024 | ||
110 | } | ||
111 | |||
112 | if (value.match(gm)) { | ||
113 | match = value.match(gm) | ||
114 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 + | ||
115 | parseInt(match[2], 10) * 1024 * 1024 | ||
116 | } | ||
117 | |||
118 | if (value.match(t)) { | ||
119 | match = value.match(t) | ||
120 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | ||
121 | } | ||
122 | |||
123 | if (value.match(g)) { | ||
124 | match = value.match(g) | ||
125 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 | ||
126 | } | ||
127 | |||
128 | if (value.match(m)) { | ||
129 | match = value.match(m) | ||
130 | return parseInt(match[1], 10) * 1024 * 1024 | ||
131 | } | ||
132 | |||
133 | if (value.match(b)) { | ||
134 | match = value.match(b) | ||
135 | return parseInt(match[1], 10) * 1024 | ||
136 | } | ||
137 | |||
138 | return parseInt(value, 10) | ||
139 | } | ||
140 | |||
141 | // --------------------------------------------------------------------------- | ||
142 | |||
143 | function sanitizeUrl (url: string) { | ||
144 | const urlObject = new URL(url) | ||
145 | |||
146 | if (urlObject.protocol === 'https:' && urlObject.port === '443') { | ||
147 | urlObject.port = '' | ||
148 | } else if (urlObject.protocol === 'http:' && urlObject.port === '80') { | ||
149 | urlObject.port = '' | ||
150 | } | ||
151 | |||
152 | return urlObject.href.replace(/\/$/, '') | ||
153 | } | ||
154 | |||
155 | // Don't import remote scheme from constants because we are in core utils | ||
156 | function sanitizeHost (host: string, remoteScheme: string) { | ||
157 | const toRemove = remoteScheme === 'https' ? 443 : 80 | ||
158 | |||
159 | return host.replace(new RegExp(`:${toRemove}$`), '') | ||
160 | } | ||
161 | |||
162 | // --------------------------------------------------------------------------- | ||
163 | |||
164 | function isTestInstance () { | ||
165 | return process.env.NODE_ENV === 'test' | ||
166 | } | ||
167 | |||
168 | function isDevInstance () { | ||
169 | return process.env.NODE_ENV === 'dev' | ||
170 | } | ||
171 | |||
172 | function isTestOrDevInstance () { | ||
173 | return isTestInstance() || isDevInstance() | ||
174 | } | ||
175 | |||
176 | function isProdInstance () { | ||
177 | return process.env.NODE_ENV === 'production' | ||
178 | } | ||
179 | |||
180 | function getAppNumber () { | ||
181 | return process.env.NODE_APP_INSTANCE || '' | ||
182 | } | ||
183 | |||
184 | // --------------------------------------------------------------------------- | ||
185 | |||
186 | // Consistent with .length, lodash truncate function is not | ||
187 | function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) { | ||
188 | const truncatedStr = truncate(str, options) | ||
189 | |||
190 | // The truncated string is okay, we can return it | ||
191 | if (truncatedStr.length <= options.length) return truncatedStr | ||
192 | |||
193 | // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2 | ||
194 | // We always use the .length so we need to truncate more if needed | ||
195 | options.length -= truncatedStr.length - options.length | ||
196 | return truncate(str, options) | ||
197 | } | ||
198 | |||
199 | function pageToStartAndCount (page: number, itemsPerPage: number) { | ||
200 | const start = (page - 1) * itemsPerPage | ||
201 | |||
202 | return { start, count: itemsPerPage } | ||
203 | } | ||
204 | |||
205 | // --------------------------------------------------------------------------- | ||
206 | |||
207 | type SemVersion = { major: number, minor: number, patch: number } | ||
208 | function parseSemVersion (s: string) { | ||
209 | const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i) | ||
210 | |||
211 | return { | ||
212 | major: parseInt(parsed[1]), | ||
213 | minor: parseInt(parsed[2]), | ||
214 | patch: parseInt(parsed[3]) | ||
215 | } as SemVersion | ||
216 | } | ||
217 | |||
218 | // --------------------------------------------------------------------------- | ||
219 | |||
220 | function execShell (command: string, options?: ExecOptions) { | ||
221 | return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { | ||
222 | exec(command, options, (err, stdout, stderr) => { | ||
223 | // eslint-disable-next-line prefer-promise-reject-errors | ||
224 | if (err) return rej({ err, stdout, stderr }) | ||
225 | |||
226 | return res({ stdout, stderr }) | ||
227 | }) | ||
228 | }) | ||
229 | } | ||
230 | |||
231 | // --------------------------------------------------------------------------- | ||
232 | |||
233 | function generateRSAKeyPairPromise (size: number) { | ||
234 | return new Promise<{ publicKey: string, privateKey: string }>((res, rej) => { | ||
235 | const options: RSAKeyPairOptions<'pem', 'pem'> = { | ||
236 | modulusLength: size, | ||
237 | publicKeyEncoding: { | ||
238 | type: 'spki', | ||
239 | format: 'pem' | ||
240 | }, | ||
241 | privateKeyEncoding: { | ||
242 | type: 'pkcs1', | ||
243 | format: 'pem' | ||
244 | } | ||
245 | } | ||
246 | |||
247 | generateKeyPair('rsa', options, (err, publicKey, privateKey) => { | ||
248 | if (err) return rej(err) | ||
249 | |||
250 | return res({ publicKey, privateKey }) | ||
251 | }) | ||
252 | }) | ||
253 | } | ||
254 | |||
255 | function generateED25519KeyPairPromise () { | ||
256 | return new Promise<{ publicKey: string, privateKey: string }>((res, rej) => { | ||
257 | const options: ED25519KeyPairOptions<'pem', 'pem'> = { | ||
258 | publicKeyEncoding: { | ||
259 | type: 'spki', | ||
260 | format: 'pem' | ||
261 | }, | ||
262 | privateKeyEncoding: { | ||
263 | type: 'pkcs8', | ||
264 | format: 'pem' | ||
265 | } | ||
266 | } | ||
267 | |||
268 | generateKeyPair('ed25519', options, (err, publicKey, privateKey) => { | ||
269 | if (err) return rej(err) | ||
270 | |||
271 | return res({ publicKey, privateKey }) | ||
272 | }) | ||
273 | }) | ||
274 | } | ||
275 | |||
276 | // --------------------------------------------------------------------------- | ||
277 | |||
278 | const randomBytesPromise = promisify1<number, Buffer>(randomBytes) | ||
279 | const scryptPromise = promisify3<string, string, number, Buffer>(scrypt) | ||
280 | const execPromise2 = promisify2<string, any, string>(exec) | ||
281 | const execPromise = promisify1<string, string>(exec) | ||
282 | const pipelinePromise = promisify(pipeline) | ||
283 | |||
284 | // --------------------------------------------------------------------------- | ||
285 | |||
286 | export { | ||
287 | isTestInstance, | ||
288 | isTestOrDevInstance, | ||
289 | isProdInstance, | ||
290 | getAppNumber, | ||
291 | |||
292 | objectConverter, | ||
293 | mapToJSON, | ||
294 | |||
295 | sanitizeUrl, | ||
296 | sanitizeHost, | ||
297 | |||
298 | execShell, | ||
299 | |||
300 | pageToStartAndCount, | ||
301 | peertubeTruncate, | ||
302 | |||
303 | scryptPromise, | ||
304 | |||
305 | randomBytesPromise, | ||
306 | |||
307 | generateRSAKeyPairPromise, | ||
308 | generateED25519KeyPairPromise, | ||
309 | |||
310 | execPromise2, | ||
311 | execPromise, | ||
312 | pipelinePromise, | ||
313 | |||
314 | parseSemVersion | ||
315 | } | ||