pve-manager/www/manager6/Parser.js
Dominik Csapak 308d9f365c ui: Parser: fix bind and dev mounts for lxc
match returns 'null' if the regex does not match, which is not
destructurable. so we have to save the match and check if it valid

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-20 21:01:49 +02:00

608 lines
13 KiB
JavaScript

// Some configuration values are complex strings -
// so we need parsers/generators for them.
Ext.define('PVE.Parser', {
statics: {
// this class only contains static functions
printACME: function(value) {
if (Ext.isArray(value.domains)) {
value.domains = value.domains.join(';');
}
return PVE.Parser.printPropertyString(value);
},
parseACME: function(value) {
if (!value) {
return {};
}
let res = {};
try {
value.split(',').forEach(property => {
let [k, v] = property.split('=', 2);
if (Ext.isDefined(v)) {
res[k] = v;
} else {
throw `Failed to parse key-value pair: ${property}`;
}
});
} catch (err) {
console.warn(err);
return undefined;
}
if (res.domains !== undefined) {
res.domains = res.domains.split(/;/);
}
return res;
},
parseBoolean: function(value, default_value) {
if (!Ext.isDefined(value)) {
return default_value;
}
value = value.toLowerCase();
return value === '1' ||
value === 'on' ||
value === 'yes' ||
value === 'true';
},
parsePropertyString: function(value, defaultKey) {
let res = {};
if (typeof value !== 'string' || value === '') {
return res;
}
try {
value.split(',').forEach(property => {
let [k, v] = property.split('=', 2);
if (Ext.isDefined(v)) {
res[k] = v;
} else if (Ext.isDefined(defaultKey)) {
if (Ext.isDefined(res[defaultKey])) {
throw 'defaultKey may be only defined once in propertyString';
}
res[defaultKey] = k; // k ist the value in this case
} else {
throw `Failed to parse key-value pair: ${property}`;
}
});
} catch (err) {
console.warn(err);
return undefined;
}
return res;
},
printPropertyString: function(data, defaultKey) {
var stringparts = [],
gotDefaultKeyVal = false,
defaultKeyVal;
Ext.Object.each(data, function(key, value) {
if (defaultKey !== undefined && key === defaultKey) {
gotDefaultKeyVal = true;
defaultKeyVal = value;
} else if (value !== '') {
stringparts.push(key + '=' + value);
}
});
stringparts = stringparts.sort();
if (gotDefaultKeyVal) {
stringparts.unshift(defaultKeyVal);
}
return stringparts.join(',');
},
parseQemuNetwork: function(key, value) {
if (!(key && value)) {
return undefined;
}
let res = {},
errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return undefined; // continue
}
let match_res;
if ((match_res = p.match(/^(ne2k_pci|e1000|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) {
res.model = match_res[1].toLowerCase();
if (match_res[3]) {
res.macaddr = match_res[3];
}
} else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
res.bridge = match_res[1];
} else if ((match_res = p.match(/^rate=(\d+(\.\d+)?)$/)) !== null) {
res.rate = match_res[1];
} else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
res.tag = match_res[1];
} else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
res.firewall = match_res[1];
} else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
res.disconnect = match_res[1];
} else if ((match_res = p.match(/^queues=(\d+)$/)) !== null) {
res.queues = match_res[1];
} else if ((match_res = p.match(/^trunks=(\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*)$/)) !== null) {
res.trunks = match_res[1];
} else if ((match_res = p.match(/^mtu=(\d+)$/)) !== null) {
res.mtu = match_res[1];
} else {
errors = true;
return false; // break
}
return undefined; // continue
});
if (errors || !res.model) {
return undefined;
}
return res;
},
printQemuNetwork: function(net) {
var netstr = net.model;
if (net.macaddr) {
netstr += "=" + net.macaddr;
}
if (net.bridge) {
netstr += ",bridge=" + net.bridge;
if (net.tag) {
netstr += ",tag=" + net.tag;
}
if (net.firewall) {
netstr += ",firewall=" + net.firewall;
}
}
if (net.rate) {
netstr += ",rate=" + net.rate;
}
if (net.queues) {
netstr += ",queues=" + net.queues;
}
if (net.disconnect) {
netstr += ",link_down=" + net.disconnect;
}
if (net.trunks) {
netstr += ",trunks=" + net.trunks;
}
if (net.mtu) {
netstr += ",mtu=" + net.mtu;
}
return netstr;
},
parseQemuDrive: function(key, value) {
if (!(key && value)) {
return undefined;
}
const [, bus, index] = key.match(/^([a-z]+)(\d+)$/);
if (!bus) {
return undefined;
}
let res = {
'interface': bus,
index,
};
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return undefined; // continue
}
let match = p.match(/^([a-z_]+)=(\S+)$/);
if (!match) {
if (!p.match(/[=]/)) {
res.file = p;
return undefined; // continue
}
errors = true;
return false; // break
}
let [, k, v] = match;
if (k === 'volume') {
k = 'file';
}
if (Ext.isDefined(res[k])) {
errors = true;
return false; // break
}
if (k === 'cache' && v === 'off') {
v = 'none';
}
res[k] = v;
return undefined; // continue
});
if (errors || !res.file) {
return undefined;
}
return res;
},
printQemuDrive: function(drive) {
var drivestr = drive.file;
Ext.Object.each(drive, function(key, value) {
if (!Ext.isDefined(value) || key === 'file' ||
key === 'index' || key === 'interface') {
return; // continue
}
drivestr += ',' + key + '=' + value;
});
return drivestr;
},
parseIPConfig: function(key, value) {
if (!(key && value)) {
return undefined; // continue
}
let res = {};
try {
value.split(',').forEach(p => {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
const match = p.match(/^(ip|gw|ip6|gw6)=(\S+)$/);
if (!match) {
throw `could not parse as IP config: ${p}`;
}
let [, k, v] = match;
res[k] = v;
});
} catch (err) {
console.warn(err);
return undefined; // continue
}
return res;
},
printIPConfig: function(cfg) {
return Object.entries(cfg)
.filter(([k, v]) => v && k.match(/^(ip|gw|ip6|gw6)$/))
.map(([k, v]) => `${k}=${v}`)
.join(',');
},
parseLxcNetwork: function(value) {
if (!value) {
return undefined;
}
let data = {};
value.split(',').forEach(p => {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
let match_res = p.match(/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|tag|rate)=(\S+)$/);
if (match_res) {
data[match_res[1]] = match_res[2];
} else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
data.firewall = PVE.Parser.parseBoolean(match_res[1]);
} else if (!p.match(/^type=\S+$/)) {
console.warn(`could not parse LXC network string ${p}`);
}
});
return data;
},
printLxcNetwork: function(config) {
let knownKeys = {
bridge: 1,
firewall: 1,
gw6: 1,
gw: 1,
hwaddr: 1,
ip6: 1,
ip: 1,
mtu: 1,
name: 1,
rate: 1,
tag: 1,
};
return Object.entries(config)
.filter(([k, v]) => v !== undefined && v !== '' && knownKeys[k])
.map(([k, v]) => `${k}=${v}`)
.join(',');
},
parseLxcMountPoint: function(value) {
if (!value) {
return undefined;
}
let res = {};
let errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return undefined; // continue
}
let match = p.match(/^([a-z_]+)=(.+)$/);
if (!match) {
if (!p.match(/[=]/)) {
res.file = p;
return undefined; // continue
}
errors = true;
return false; // break
}
let [, k, v] = match;
if (k === 'volume') {
k = 'file';
}
if (Ext.isDefined(res[k])) {
errors = true;
return false; // break
}
res[k] = v;
return undefined;
});
if (errors || !res.file) {
return undefined;
}
const match = res.file.match(/^([a-z][a-z0-9\-_.]*[a-z0-9]):/i);
if (match) {
res.storage = match[1];
res.type = 'volume';
} else if (res.file.match(/^\/dev\//)) {
res.type = 'device';
} else {
res.type = 'bind';
}
return res;
},
printLxcMountPoint: function(mp) {
let drivestr = mp.file;
for (const [key, value] of Object.entries(mp)) {
if (!Ext.isDefined(value) || key === 'file' || key === 'type' || key === 'storage') {
continue;
}
drivestr += `,${key}=${value}`;
}
return drivestr;
},
parseStartup: function(value) {
if (value === undefined) {
return undefined;
}
let res = {};
try {
value.split(',').forEach(p => {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
let match_res;
if ((match_res = p.match(/^(order)?=(\d+)$/)) !== null) {
res.order = match_res[2];
} else if ((match_res = p.match(/^up=(\d+)$/)) !== null) {
res.up = match_res[1];
} else if ((match_res = p.match(/^down=(\d+)$/)) !== null) {
res.down = match_res[1];
} else {
throw `could not parse startup config ${p}`;
}
});
} catch (err) {
console.warn(err);
return undefined;
}
return res;
},
printStartup: function(startup) {
let arr = [];
if (startup.order !== undefined && startup.order !== '') {
arr.push('order=' + startup.order);
}
if (startup.up !== undefined && startup.up !== '') {
arr.push('up=' + startup.up);
}
if (startup.down !== undefined && startup.down !== '') {
arr.push('down=' + startup.down);
}
return arr.join(',');
},
parseQemuSmbios1: function(value) {
let res = value.split(',').reduce((acc, currentValue) => {
const [k, v] = currentValue.split(/[=](.+)/);
acc[k] = v;
return acc;
}, {});
if (PVE.Parser.parseBoolean(res.base64, false)) {
for (const [k, v] of Object.entries(res)) {
if (k !== 'uuid') {
res[k] = Ext.util.Base64.decode(v);
}
}
}
return res;
},
printQemuSmbios1: function(data) {
let base64 = false;
let datastr = Object.entries(data)
.map(([key, value]) => {
if (value === '') {
return undefined;
}
if (key !== 'uuid') {
base64 = true; // smbios values can be arbitrary, so encode and mark config as such
value = Ext.util.Base64.encode(value);
}
return `${key}=${value}`;
})
.filter(v => v !== undefined)
.join(',');
if (base64) {
datastr += ',base64=1';
}
return datastr;
},
parseTfaConfig: function(value) {
let res = {};
value.split(',').forEach(p => {
const [k, v] = p.split('=', 2);
res[k] = v;
});
return res;
},
parseTfaType: function(value) {
let match;
if (!value || !value.length) {
return undefined;
} else if (value === 'x!oath') {
return 'totp';
} else if ((match = value.match(/^x!(.+)$/)) !== null) {
return match[1];
} else {
return 1;
}
},
parseQemuCpu: function(value) {
if (!value) {
return {};
}
let res = {};
let errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return undefined; // continue
}
if (!p.match(/[=]/)) {
if (Ext.isDefined(res.cpu)) {
errors = true;
return false; // break
}
res.cputype = p;
return undefined; // continue
}
let match = p.match(/^([a-z_]+)=(\S+)$/);
if (!match || Ext.isDefined(res[match[1]])) {
errors = true;
return false; // break
}
let [, k, v] = match;
res[k] = v;
return undefined;
});
if (errors || !res.cputype) {
return undefined;
}
return res;
},
printQemuCpu: function(cpu) {
let cpustr = cpu.cputype;
let optstr = '';
Ext.Object.each(cpu, function(key, value) {
if (!Ext.isDefined(value) || key === 'cputype') {
return; // continue
}
optstr += ',' + key + '=' + value;
});
if (!cpustr) {
if (optstr) {
return 'kvm64' + optstr;
} else {
return undefined;
}
}
return cpustr + optstr;
},
parseSSHKey: function(key) {
// |--- options can have quotes--| type key comment
let keyre = /^(?:((?:[^\s"]|"(?:\\.|[^"\\])*")+)\s+)?(\S+)\s+(\S+)(?:\s+(.*))?$/;
let typere = /^(?:(?:sk-)?ssh-(?:dss|rsa|ed25519)|ecdsa-sha2-nistp\d+)$/;
let m = key.match(keyre);
if (!m || m.length < 3 || !m[2]) { // [2] is always either type or key
return null;
}
if (m[1] && m[1].match(typere)) {
return {
type: m[1],
key: m[2],
comment: m[3],
};
}
if (m[2].match(typere)) {
return {
options: m[1],
type: m[2],
key: m[3],
comment: m[4],
};
}
return null;
},
parseACMEPluginData: function(data) {
let res = {};
let extradata = [];
data.split('\n').forEach((line) => {
// capture everything after the first = as value
let [key, value] = line.split(/[=](.+)/);
if (value !== undefined) {
res[key] = value;
} else {
extradata.push(line);
}
});
return [res, extradata];
},
},
});