]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/Parser.js
firewall: expose configuration option for new nftables firewall
[pve-manager.git] / www / manager6 / Parser.js
CommitLineData
b85ef5ab 1// Some configuration values are complex strings - so we need parsers/generators for them.
8058410f
TL
2Ext.define('PVE.Parser', {
3 statics: {
fcb64fe4
DM
4
5 // this class only contains static functions
6
899c4f45
DC
7 printACME: function(value) {
8 if (Ext.isArray(value.domains)) {
9 value.domains = value.domains.join(';');
10 }
11 return PVE.Parser.printPropertyString(value);
12 },
13
488be4c2
DC
14 parseACME: function(value) {
15 if (!value) {
6ac64c3a 16 return {};
488be4c2
DC
17 }
18
a3aef0f5
TL
19 let res = {};
20 try {
21 value.split(',').forEach(property => {
22 let [k, v] = property.split('=', 2);
23 if (Ext.isDefined(v)) {
24 res[k] = v;
25 } else {
26 throw `Failed to parse key-value pair: ${property}`;
27 }
28 });
29 } catch (err) {
30 console.warn(err);
31 return undefined;
488be4c2
DC
32 }
33
08a61c7b
FG
34 if (res.domains !== undefined) {
35 res.domains = res.domains.split(/;/);
36 }
37
488be4c2
DC
38 return res;
39 },
40
4c1c0d5d 41 parseBoolean: function(value, default_value) {
84de645d 42 if (!Ext.isDefined(value)) {
4c1c0d5d 43 return default_value;
84de645d 44 }
4c1c0d5d 45 value = value.toLowerCase();
ec0bd652 46 return value === '1' ||
4c1c0d5d
EK
47 value === 'on' ||
48 value === 'yes' ||
49 value === 'true';
50 },
51
3d6189d3 52 parsePropertyString: function(value, defaultKey) {
a3aef0f5 53 let res = {};
3d6189d3 54
4a7248d4
TL
55 if (typeof value !== 'string' || value === '') {
56 return res;
57 }
58
a3aef0f5
TL
59 try {
60 value.split(',').forEach(property => {
61 let [k, v] = property.split('=', 2);
62 if (Ext.isDefined(v)) {
63 res[k] = v;
64 } else if (Ext.isDefined(defaultKey)) {
65 if (Ext.isDefined(res[defaultKey])) {
66 throw 'defaultKey may be only defined once in propertyString';
67 }
68 res[defaultKey] = k; // k ist the value in this case
69 } else {
70 throw `Failed to parse key-value pair: ${property}`;
3d6189d3 71 }
a3aef0f5
TL
72 });
73 } catch (err) {
74 console.warn(err);
75 return undefined;
3d6189d3
SI
76 }
77
78 return res;
79 },
80
81 printPropertyString: function(data, defaultKey) {
6de2baee
TL
82 var stringparts = [],
83 gotDefaultKeyVal = false,
84 defaultKeyVal;
3d6189d3
SI
85
86 Ext.Object.each(data, function(key, value) {
8007aaaf 87 if (defaultKey !== undefined && key === defaultKey) {
6de2baee
TL
88 gotDefaultKeyVal = true;
89 defaultKeyVal = value;
08e38f03 90 } else if (value !== '') {
8007aaaf 91 stringparts.push(key + '=' + value);
3d6189d3 92 }
3d6189d3
SI
93 });
94
6de2baee
TL
95 stringparts = stringparts.sort();
96 if (gotDefaultKeyVal) {
97 stringparts.unshift(defaultKeyVal);
98 }
99
3d6189d3
SI
100 return stringparts.join(',');
101 },
102
fcb64fe4
DM
103 parseQemuNetwork: function(key, value) {
104 if (!(key && value)) {
a3aef0f5 105 return undefined;
fcb64fe4
DM
106 }
107
a3aef0f5
TL
108 let res = {},
109 errors = false;
fcb64fe4
DM
110 Ext.Array.each(value.split(','), function(p) {
111 if (!p || p.match(/^\s*$/)) {
a3aef0f5 112 return undefined; // continue
fcb64fe4
DM
113 }
114
a3aef0f5 115 let match_res;
fcb64fe4 116
8e460b76 117 if ((match_res = p.match(/^(ne2k_pci|e1000e?|e1000-82540em|e1000-82544gc|e1000-82545em|vmxnet3|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i)) !== null) {
fcb64fe4
DM
118 res.model = match_res[1].toLowerCase();
119 if (match_res[3]) {
120 res.macaddr = match_res[3];
121 }
122 } else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
123 res.bridge = match_res[1];
b120875c 124 } else if ((match_res = p.match(/^rate=(\d+(\.\d+)?|\.\d+)$/)) !== null) {
fcb64fe4
DM
125 res.rate = match_res[1];
126 } else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
63e62a6f 127 res.tag = match_res[1];
fcb64fe4 128 } else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
63e62a6f 129 res.firewall = match_res[1];
fcb64fe4 130 } else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
63e62a6f 131 res.disconnect = match_res[1];
fcb64fe4 132 } else if ((match_res = p.match(/^queues=(\d+)$/)) !== null) {
63e62a6f 133 res.queues = match_res[1];
a2ed0697
WB
134 } else if ((match_res = p.match(/^trunks=(\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*)$/)) !== null) {
135 res.trunks = match_res[1];
ced67201
DC
136 } else if ((match_res = p.match(/^mtu=(\d+)$/)) !== null) {
137 res.mtu = match_res[1];
fcb64fe4
DM
138 } else {
139 errors = true;
140 return false; // break
141 }
a3aef0f5 142 return undefined; // continue
fcb64fe4
DM
143 });
144
145 if (errors || !res.model) {
a3aef0f5 146 return undefined;
fcb64fe4
DM
147 }
148
149 return res;
150 },
151
152 printQemuNetwork: function(net) {
fcb64fe4
DM
153 var netstr = net.model;
154 if (net.macaddr) {
155 netstr += "=" + net.macaddr;
156 }
157 if (net.bridge) {
158 netstr += ",bridge=" + net.bridge;
159 if (net.tag) {
160 netstr += ",tag=" + net.tag;
161 }
162 if (net.firewall) {
163 netstr += ",firewall=" + net.firewall;
164 }
165 }
166 if (net.rate) {
167 netstr += ",rate=" + net.rate;
168 }
169 if (net.queues) {
170 netstr += ",queues=" + net.queues;
171 }
172 if (net.disconnect) {
173 netstr += ",link_down=" + net.disconnect;
174 }
a2ed0697
WB
175 if (net.trunks) {
176 netstr += ",trunks=" + net.trunks;
177 }
ced67201
DC
178 if (net.mtu) {
179 netstr += ",mtu=" + net.mtu;
180 }
fcb64fe4
DM
181 return netstr;
182 },
183
184 parseQemuDrive: function(key, value) {
185 if (!(key && value)) {
a3aef0f5 186 return undefined;
fcb64fe4
DM
187 }
188
a3aef0f5
TL
189 const [, bus, index] = key.match(/^([a-z]+)(\d+)$/);
190 if (!bus) {
191 return undefined;
fcb64fe4 192 }
a3aef0f5
TL
193 let res = {
194 'interface': bus,
195 index,
196 };
fcb64fe4
DM
197
198 var errors = false;
199 Ext.Array.each(value.split(','), function(p) {
200 if (!p || p.match(/^\s*$/)) {
a3aef0f5 201 return undefined; // continue
fcb64fe4 202 }
a3aef0f5
TL
203 let match = p.match(/^([a-z_]+)=(\S+)$/);
204 if (!match) {
205 if (!p.match(/[=]/)) {
fcb64fe4 206 res.file = p;
a3aef0f5 207 return undefined; // continue
fcb64fe4
DM
208 }
209 errors = true;
210 return false; // break
211 }
a3aef0f5 212 let [, k, v] = match;
fcb64fe4
DM
213 if (k === 'volume') {
214 k = 'file';
215 }
216
217 if (Ext.isDefined(res[k])) {
218 errors = true;
219 return false; // break
220 }
221
fcb64fe4
DM
222 if (k === 'cache' && v === 'off') {
223 v = 'none';
224 }
225
226 res[k] = v;
a3aef0f5
TL
227
228 return undefined; // continue
fcb64fe4
DM
229 });
230
231 if (errors || !res.file) {
a3aef0f5 232 return undefined;
fcb64fe4
DM
233 }
234
235 return res;
236 },
237
238 printQemuDrive: function(drive) {
fcb64fe4
DM
239 var drivestr = drive.file;
240
241 Ext.Object.each(drive, function(key, value) {
242 if (!Ext.isDefined(value) || key === 'file' ||
243 key === 'index' || key === 'interface') {
244 return; // continue
245 }
246 drivestr += ',' + key + '=' + value;
247 });
248
249 return drivestr;
250 },
251
01e02121
DC
252 parseIPConfig: function(key, value) {
253 if (!(key && value)) {
a3aef0f5 254 return undefined; // continue
01e02121
DC
255 }
256
a3aef0f5
TL
257 let res = {};
258 try {
259 value.split(',').forEach(p => {
260 if (!p || p.match(/^\s*$/)) {
261 return; // continue
262 }
01e02121 263
a3aef0f5
TL
264 const match = p.match(/^(ip|gw|ip6|gw6)=(\S+)$/);
265 if (!match) {
266 throw `could not parse as IP config: ${p}`;
267 }
268 let [, k, v] = match;
269 res[k] = v;
270 });
271 } catch (err) {
272 console.warn(err);
273 return undefined; // continue
01e02121
DC
274 }
275
276 return res;
277 },
278
279 printIPConfig: function(cfg) {
a3aef0f5
TL
280 return Object.entries(cfg)
281 .filter(([k, v]) => v && k.match(/^(ip|gw|ip6|gw6)$/))
282 .map(([k, v]) => `${k}=${v}`)
283 .join(',');
01e02121
DC
284 },
285
fcb64fe4
DM
286 parseLxcNetwork: function(value) {
287 if (!value) {
a3aef0f5 288 return undefined;
fcb64fe4
DM
289 }
290
a3aef0f5
TL
291 let data = {};
292 value.split(',').forEach(p => {
fcb64fe4
DM
293 if (!p || p.match(/^\s*$/)) {
294 return; // continue
295 }
a3aef0f5 296 let match_res = p.match(/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|tag|rate)=(\S+)$/);
95e77b22
TL
297 if (match_res) {
298 data[match_res[1]] = match_res[2];
299 } else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
805a7cb8 300 data.firewall = PVE.Parser.parseBoolean(match_res[1]);
4c6c99cc
CH
301 } else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
302 data.link_down = PVE.Parser.parseBoolean(match_res[1]);
a3aef0f5
TL
303 } else if (!p.match(/^type=\S+$/)) {
304 console.warn(`could not parse LXC network string ${p}`);
fcb64fe4 305 }
fcb64fe4
DM
306 });
307
308 return data;
309 },
310
a3aef0f5
TL
311 printLxcNetwork: function(config) {
312 let knownKeys = {
313 bridge: 1,
314 firewall: 1,
315 gw6: 1,
316 gw: 1,
317 hwaddr: 1,
318 ip6: 1,
319 ip: 1,
320 mtu: 1,
321 name: 1,
322 rate: 1,
323 tag: 1,
4c6c99cc 324 link_down: 1,
a3aef0f5
TL
325 };
326 return Object.entries(config)
f155d934 327 .filter(([k, v]) => v !== undefined && v !== '' && knownKeys[k])
a3aef0f5
TL
328 .map(([k, v]) => `${k}=${v}`)
329 .join(',');
fcb64fe4
DM
330 },
331
4c1c0d5d
EK
332 parseLxcMountPoint: function(value) {
333 if (!value) {
a3aef0f5 334 return undefined;
4c1c0d5d
EK
335 }
336
a3aef0f5
TL
337 let res = {};
338 let errors = false;
4c1c0d5d
EK
339 Ext.Array.each(value.split(','), function(p) {
340 if (!p || p.match(/^\s*$/)) {
a3aef0f5 341 return undefined; // continue
4c1c0d5d 342 }
a3aef0f5
TL
343 let match = p.match(/^([a-z_]+)=(.+)$/);
344 if (!match) {
345 if (!p.match(/[=]/)) {
4c1c0d5d 346 res.file = p;
a3aef0f5 347 return undefined; // continue
4c1c0d5d
EK
348 }
349 errors = true;
350 return false; // break
351 }
a3aef0f5 352 let [, k, v] = match;
4c1c0d5d
EK
353 if (k === 'volume') {
354 k = 'file';
355 }
356
357 if (Ext.isDefined(res[k])) {
358 errors = true;
359 return false; // break
360 }
361
4c1c0d5d 362 res[k] = v;
a3aef0f5
TL
363
364 return undefined;
4c1c0d5d
EK
365 });
366
367 if (errors || !res.file) {
a3aef0f5 368 return undefined;
4c1c0d5d
EK
369 }
370
308d9f36
DC
371 const match = res.file.match(/^([a-z][a-z0-9\-_.]*[a-z0-9]):/i);
372 if (match) {
373 res.storage = match[1];
4c1c0d5d
EK
374 res.type = 'volume';
375 } else if (res.file.match(/^\/dev\//)) {
376 res.type = 'device';
377 } else {
378 res.type = 'bind';
379 }
380
381 return res;
382 },
383
384 printLxcMountPoint: function(mp) {
a3aef0f5 385 let drivestr = mp.file;
c57ff36d 386 for (const [key, value] of Object.entries(mp)) {
a3aef0f5
TL
387 if (!Ext.isDefined(value) || key === 'file' || key === 'type' || key === 'storage') {
388 continue;
4c1c0d5d 389 }
a3aef0f5
TL
390 drivestr += `,${key}=${value}`;
391 }
4c1c0d5d
EK
392 return drivestr;
393 },
394
fcb64fe4
DM
395 parseStartup: function(value) {
396 if (value === undefined) {
a3aef0f5 397 return undefined;
fcb64fe4
DM
398 }
399
a3aef0f5
TL
400 let res = {};
401 try {
402 value.split(',').forEach(p => {
403 if (!p || p.match(/^\s*$/)) {
404 return; // continue
405 }
fcb64fe4 406
a3aef0f5
TL
407 let match_res;
408 if ((match_res = p.match(/^(order)?=(\d+)$/)) !== null) {
409 res.order = match_res[2];
410 } else if ((match_res = p.match(/^up=(\d+)$/)) !== null) {
411 res.up = match_res[1];
412 } else if ((match_res = p.match(/^down=(\d+)$/)) !== null) {
413 res.down = match_res[1];
414 } else {
415 throw `could not parse startup config ${p}`;
416 }
417 });
418 } catch (err) {
419 console.warn(err);
420 return undefined;
fcb64fe4
DM
421 }
422
423 return res;
424 },
425
426 printStartup: function(startup) {
a3aef0f5 427 let arr = [];
fcb64fe4
DM
428 if (startup.order !== undefined && startup.order !== '') {
429 arr.push('order=' + startup.order);
430 }
431 if (startup.up !== undefined && startup.up !== '') {
432 arr.push('up=' + startup.up);
433 }
434 if (startup.down !== undefined && startup.down !== '') {
435 arr.push('down=' + startup.down);
436 }
437
438 return arr.join(',');
439 },
440
441 parseQemuSmbios1: function(value) {
a3aef0f5
TL
442 let res = value.split(',').reduce((acc, currentValue) => {
443 const [k, v] = currentValue.split(/[=](.+)/);
444 acc[k] = v;
445 return acc;
150ad74a
CE
446 }, {});
447
448 if (PVE.Parser.parseBoolean(res.base64, false)) {
a3aef0f5
TL
449 for (const [k, v] of Object.entries(res)) {
450 if (k !== 'uuid') {
451 res[k] = Ext.util.Base64.decode(v);
452 }
453 }
150ad74a 454 }
fcb64fe4
DM
455
456 return res;
457 },
458
459 printQemuSmbios1: function(data) {
a3aef0f5
TL
460 let base64 = false;
461 let datastr = Object.entries(data)
462 .map(([key, value]) => {
463 if (value === '') {
464 return undefined;
150ad74a 465 }
a3aef0f5
TL
466 if (key !== 'uuid') {
467 base64 = true; // smbios values can be arbitrary, so encode and mark config as such
468 value = Ext.util.Base64.encode(value);
469 }
470 return `${key}=${value}`;
471 })
472 .filter(v => v !== undefined)
473 .join(',');
fcb64fe4 474
a3aef0f5
TL
475 if (base64) {
476 datastr += ',base64=1';
477 }
fcb64fe4
DM
478 return datastr;
479 },
480
481 parseTfaConfig: function(value) {
a3aef0f5
TL
482 let res = {};
483 value.split(',').forEach(p => {
484 const [k, v] = p.split('=', 2);
485 res[k] = v;
fcb64fe4
DM
486 });
487
488 return res;
4c1c0d5d
EK
489 },
490
1cdb49c1 491 parseTfaType: function(value) {
a3aef0f5 492 let match;
1cdb49c1
WB
493 if (!value || !value.length) {
494 return undefined;
495 } else if (value === 'x!oath') {
496 return 'totp';
a3aef0f5 497 } else if ((match = value.match(/^x!(.+)$/)) !== null) {
1cdb49c1
WB
498 return match[1];
499 } else {
500 return 1;
501 }
502 },
503
4c1c0d5d
EK
504 parseQemuCpu: function(value) {
505 if (!value) {
506 return {};
507 }
508
a3aef0f5
TL
509 let res = {};
510 let errors = false;
4c1c0d5d
EK
511 Ext.Array.each(value.split(','), function(p) {
512 if (!p || p.match(/^\s*$/)) {
a3aef0f5 513 return undefined; // continue
4c1c0d5d 514 }
fcb64fe4 515
a3aef0f5 516 if (!p.match(/[=]/)) {
ec0bd652 517 if (Ext.isDefined(res.cpu)) {
4c1c0d5d
EK
518 errors = true;
519 return false; // break
520 }
521 res.cputype = p;
a3aef0f5 522 return undefined; // continue
4c1c0d5d
EK
523 }
524
a3aef0f5
TL
525 let match = p.match(/^([a-z_]+)=(\S+)$/);
526 if (!match || Ext.isDefined(res[match[1]])) {
4c1c0d5d
EK
527 errors = true;
528 return false; // break
529 }
530
a3aef0f5
TL
531 let [, k, v] = match;
532 res[k] = v;
4c1c0d5d 533
a3aef0f5 534 return undefined;
4c1c0d5d
EK
535 });
536
537 if (errors || !res.cputype) {
a3aef0f5 538 return undefined;
4c1c0d5d
EK
539 }
540
541 return res;
542 },
543
544 printQemuCpu: function(cpu) {
a3aef0f5
TL
545 let cpustr = cpu.cputype;
546 let optstr = '';
4c1c0d5d
EK
547
548 Ext.Object.each(cpu, function(key, value) {
549 if (!Ext.isDefined(value) || key === 'cputype') {
550 return; // continue
551 }
552 optstr += ',' + key + '=' + value;
553 });
554
555 if (!cpustr) {
84de645d 556 if (optstr) {
4c1c0d5d 557 return 'kvm64' + optstr;
a3aef0f5
TL
558 } else {
559 return undefined;
84de645d 560 }
4c1c0d5d
EK
561 }
562
563 return cpustr + optstr;
b1339314
WB
564 },
565
566 parseSSHKey: function(key) {
567 // |--- options can have quotes--| type key comment
a3aef0f5 568 let keyre = /^(?:((?:[^\s"]|"(?:\\.|[^"\\])*")+)\s+)?(\S+)\s+(\S+)(?:\s+(.*))?$/;
2cec9697 569 let typere = /^(?:(?:sk-)?(?:ssh-(?:dss|rsa|ed25519)|ecdsa-sha2-nistp\d+)(?:@(?:[a-z0-9_-]+\.)+[a-z]{2,})?)$/;
b1339314 570
a3aef0f5
TL
571 let m = key.match(keyre);
572 if (!m || m.length < 3 || !m[2]) { // [2] is always either type or key
b1339314
WB
573 return null;
574 }
575 if (m[1] && m[1].match(typere)) {
576 return {
577 type: m[1],
578 key: m[2],
f6710aac 579 comment: m[3],
b1339314
WB
580 };
581 }
582 if (m[2].match(typere)) {
583 return {
584 options: m[1],
585 type: m[2],
586 key: m[3],
f6710aac 587 comment: m[4],
b1339314
WB
588 };
589 }
590 return null;
45708891
DC
591 },
592
593 parseACMEPluginData: function(data) {
594 let res = {};
595 let extradata = [];
596 data.split('\n').forEach((line) => {
597 // capture everything after the first = as value
a3aef0f5 598 let [key, value] = line.split(/[=](.+)/);
45708891
DC
599 if (value !== undefined) {
600 res[key] = value;
601 } else {
602 extradata.push(line);
603 }
604 });
605 return [res, extradata];
606 },
d5a96067
DC
607
608 filterPropertyStringList: function(list, filterFn, defaultKey) {
609 return list.filter((entry) => filterFn(PVE.Parser.parsePropertyString(entry, defaultKey)));
610 },
fa8d3971 611},
8058410f 612});