From 73a9ee783456aab898e9c78cba12317fb54279ae Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Fri, 21 May 2021 14:49:58 +0200 Subject: [PATCH] generate-reader.uc: implement string format validation - Generate code for validating formats - Remove dead code - Fix a wrong schema format discovered by the resulting reader code Signed-off-by: Jo-Philipp Wich --- generate-reader.uc | 105 +++++++++++++++++++++++--------- schema/interface.ipv4.yml | 2 +- schemareader.uc | 122 ++++++++++++++++++++++++++++++++++++++ ucentral.schema.json | 2 +- 4 files changed, 202 insertions(+), 29 deletions(-) diff --git a/generate-reader.uc b/generate-reader.uc index ddfac8e..88c8569 100755 --- a/generate-reader.uc +++ b/generate-reader.uc @@ -48,6 +48,60 @@ function to_value_descr(path) let GeneratorProto = { + format_validators: { + "uc-cidr4": [ + 'let m = match(s, /^(auto|[0-9.]+)\\/([0-9]+)$/);', + 'return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false;' + ], + "uc-cidr6": [ + 'let m = match(s, /^(auto|[0-9a-fA-F:.]+)\\/([0-9]+)$/);', + 'return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 16) && +m[2] <= 128) : false;' + ], + "uc-cidr": [ + 'let m = match(s, /^(auto|[0-9a-fA-F:.]+)\\/([0-9]+)$/);', + 'if (!m) return false;', + 'let l = (m[1] == "auto") ? 16 : length(iptoarr(m[1]));', + 'return (l > 0 && +m[2] <= (l * 8));' + ], + "uc-mac": [ + 'return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i);' + ], + "uc-host": [ + 'if (length(iptoarr(s)) != 0) return true;', + 'if (length(s) > 255) return false;', + 'let labels = split(s, ".");', + 'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);' + ], + "uc-timeout": [ + 'return match(s, /^[0-9]+[smhdw]$/);' + ], + "hostname": [ + 'if (length(s) > 255) return false;', + 'let labels = split(s, ".");', + 'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);' + ], + "fqdn": [ + 'if (length(s) > 255) return false;', + 'let labels = split(s, ".");', + 'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 1);' + ], + "ipv4": [ + 'return (length(iptoarr(s)) == 4);' + ], + "ipv6": [ + 'return (length(iptoarr(s)) == 16);' + ], + "uri": [ + 'if (index(s, "data:") == 0) return true;', + 'let m = match(s, /^[a-z+-]+:\\/\\/([^\\/]+).*$/);', + 'if (!m) return false;', + 'if (length(iptoarr(m[1])) != 0) return true;', + 'if (length(m[1]) > 255) return false;', + 'let labels = split(m[1], ".");', + 'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);' + ] + }, + is_ref: function(value) { return ( @@ -93,6 +147,29 @@ let GeneratorProto = { return ref; }, + emit_format_asserts: function(path, valueExpr, valueSpec) + { + if (!valueSpec.format) + return; + + if (!exists(this.format_validators, valueSpec.format)) { + warn("Unrecognized string format '" + valueSpec.format + '".\n'); + return; + } + + let code = this.format_validators[valueSpec.format]; + + this.print(path, 'assert((s => {'); + + for (let line in code) + this.print(path, ' ' + line); + + this.print(path, '})(%s), "%s has invalid format: " + %s);', + valueExpr, + to_value_descr(path), + valueExpr); + }, + emit_generic_asserts: function(path, valueExpr, valueSpec) { if (type(valueSpec.enum) == 'array' && length(valueSpec.enum) > 0) @@ -185,33 +262,6 @@ let GeneratorProto = { } }, - emit_constraints: function(propertyName, valueSpec) - { - this.emit_generic_asserts(propertyName, valueSpec); - - switch (valueSpec.type) { - case 'integer': - this.emit_number_asserts(propertyName, valueSpec, true); - break; - - case 'number': - this.emit_number_asserts(propertyName, valueSpec, false); - break; - - case 'string': - this.emit_string_asserts(propertyName, valueSpec); - break; - - case 'array': - this.emit_array_asserts(propertyName, valueSpec); - break; - - case 'object': - this.emit_object_asserts(propertyName, valueSpec); - break; - } - }, - read_schema: function(path) { let fd = fs.open(path, "r"); @@ -271,6 +321,7 @@ let GeneratorProto = { } this.emit_generic_asserts(path, valueExpr, valueSpec); + this.emit_format_asserts(path, valueExpr, valueSpec); let variantSpecs, variantSuccessCond; diff --git a/schema/interface.ipv4.yml b/schema/interface.ipv4.yml index f77f2f4..882828e 100644 --- a/schema/interface.ipv4.yml +++ b/schema/interface.ipv4.yml @@ -25,7 +25,7 @@ properties: description: This option defines the static IPv4 gateway of the logical interface. type: string - format: uc-cidr4 + format: ipv4 examples: - 192.168.1.1 send-hostname: diff --git a/schemareader.uc b/schemareader.uc index 3a56d56..6dce61a 100644 --- a/schemareader.uc +++ b/schemareader.uc @@ -32,11 +32,19 @@ function instantiateGlobals(value) { if (exists(value, "ipv4-network")) { assert(type(value["ipv4-network"]) == "string", "Property globals.ipv4-network must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false; + })(value["ipv4-network"]), "Property globals.ipv4-network has invalid format: " + value["ipv4-network"]); obj.ipv4_network = value["ipv4-network"]; } if (exists(value, "ipv6-network")) { assert(type(value["ipv6-network"]) == "string", "Property globals.ipv6-network must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9a-fA-F:.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 16) && +m[2] <= 128) : false; + })(value["ipv6-network"]), "Property globals.ipv6-network has invalid format: " + value["ipv6-network"]); obj.ipv6_network = value["ipv6-network"]; } @@ -329,6 +337,9 @@ function instantiateInterfaceEthernet(value) { if (exists(value, "macaddr")) { assert(type(value["macaddr"]) == "string", "Property interface.ethernet.macaddr must be of type string"); + assert((s => { + return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i); + })(value["macaddr"]), "Property interface.ethernet.macaddr has invalid format: " + value["macaddr"]); obj.macaddr = value["macaddr"]; } @@ -360,6 +371,9 @@ function instantiateInterfaceIpv4Dhcp(value) { if (exists(value, "lease-time")) { assert(type(value["lease-time"]) == "string", "Property interface.ipv4.dhcp.lease-time must be of type string"); + assert((s => { + return match(s, /^[0-9]+[smhdw]$/); + })(value["lease-time"]), "Property interface.ipv4.dhcp.lease-time has invalid format: " + value["lease-time"]); obj.lease_time = value["lease-time"]; } else { @@ -376,6 +390,9 @@ function instantiateInterfaceIpv4DhcpLease(value) { if (exists(value, "macaddr")) { assert(type(value["macaddr"]) == "string", "Property interface.ipv4.dhcp-lease.macaddr must be of type string"); + assert((s => { + return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i); + })(value["macaddr"]), "Property interface.ipv4.dhcp-lease.macaddr has invalid format: " + value["macaddr"]); obj.macaddr = value["macaddr"]; } @@ -386,6 +403,9 @@ function instantiateInterfaceIpv4DhcpLease(value) { if (exists(value, "lease-time")) { assert(type(value["lease-time"]) == "string", "Property interface.ipv4.dhcp-lease.lease-time must be of type string"); + assert((s => { + return match(s, /^[0-9]+[smhdw]$/); + })(value["lease-time"]), "Property interface.ipv4.dhcp-lease.lease-time has invalid format: " + value["lease-time"]); obj.lease_time = value["lease-time"]; } else { @@ -416,11 +436,18 @@ function instantiateInterfaceIpv4(value) { if (exists(value, "subnet")) { assert(type(value["subnet"]) == "string", "Property interface.ipv4.subnet must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false; + })(value["subnet"]), "Property interface.ipv4.subnet has invalid format: " + value["subnet"]); obj.subnet = value["subnet"]; } if (exists(value, "gateway")) { assert(type(value["gateway"]) == "string", "Property interface.ipv4.gateway must be of type string"); + assert((s => { + return (length(iptoarr(s)) == 4); + })(value["gateway"]), "Property interface.ipv4.gateway has invalid format: " + value["gateway"]); obj.gateway = value["gateway"]; } @@ -437,6 +464,9 @@ function instantiateInterfaceIpv4(value) { return map(value, (item) => { assert(type(item) == "string", "Items of interface.ipv4.use-dns must be of type string"); + assert((s => { + return (length(iptoarr(s)) == 4); + })(item), "Items of interface.ipv4.use-dns has invalid format: " + item); return item; }); } @@ -478,6 +508,9 @@ function instantiateInterfaceIpv6Dhcpv6(value) { return map(value, (item) => { assert(type(item) == "string", "Items of interface.ipv6.dhcpv6.announce-dns must be of type string"); + assert((s => { + return (length(iptoarr(s)) == 16); + })(item), "Items of interface.ipv6.dhcpv6.announce-dns has invalid format: " + item); return item; }); } @@ -488,6 +521,10 @@ function instantiateInterfaceIpv6Dhcpv6(value) { if (exists(value, "filter-prefix")) { assert(type(value["filter-prefix"]) == "string", "Property interface.ipv6.dhcpv6.filter-prefix must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9a-fA-F:.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 16) && +m[2] <= 128) : false; + })(value["filter-prefix"]), "Property interface.ipv6.dhcpv6.filter-prefix has invalid format: " + value["filter-prefix"]); obj.filter_prefix = value["filter-prefix"]; } else { @@ -510,11 +547,18 @@ function instantiateInterfaceIpv6(value) { if (exists(value, "subnet")) { assert(type(value["subnet"]) == "string", "Property interface.ipv6.subnet must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9a-fA-F:.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 16) && +m[2] <= 128) : false; + })(value["subnet"]), "Property interface.ipv6.subnet has invalid format: " + value["subnet"]); obj.subnet = value["subnet"]; } if (exists(value, "gateway")) { assert(type(value["gateway"]) == "string", "Property interface.ipv6.gateway must be of type string"); + assert((s => { + return (length(iptoarr(s)) == 16); + })(value["gateway"]), "Property interface.ipv6.gateway has invalid format: " + value["gateway"]); obj.gateway = value["gateway"]; } @@ -571,6 +615,9 @@ function instantiateInterfaceSsidMultiPsk(value) { if (exists(value, "mac")) { assert(type(value["mac"]) == "string", "Property interface.ssid.multi-psk.mac must be of type string"); + assert((s => { + return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i); + })(value["mac"]), "Property interface.ssid.multi-psk.mac has invalid format: " + value["mac"]); obj.mac = value["mac"]; } @@ -607,6 +654,11 @@ function instantiateInterfaceSsidCaptive(value) { if (exists(value, "gateway-fqdn")) { assert(type(value["gateway-fqdn"]) == "string", "Property interface.ssid.captive.gateway-fqdn must be of type string"); + assert((s => { + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 1); + })(value["gateway-fqdn"]), "Property interface.ssid.captive.gateway-fqdn has invalid format: " + value["gateway-fqdn"]); obj.gateway_fqdn = value["gateway-fqdn"]; } else { @@ -789,6 +841,9 @@ function instantiateInterfaceSsidRadiusLocalUser(value) { if (exists(value, "mac")) { assert(type(value["mac"]) == "string", "Property interface.ssid.radius.local-user.mac must be of type string"); + assert((s => { + return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i); + })(value["mac"]), "Property interface.ssid.radius.local-user.mac has invalid format: " + value["mac"]); obj.mac = value["mac"]; } @@ -826,6 +881,12 @@ function instantiateInterfaceSsidRadiusServer(value) { if (exists(value, "host")) { assert(type(value["host"]) == "string", "Property interface.ssid.radius.server.host must be of type string"); + assert((s => { + if (length(iptoarr(s)) != 0) return true; + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["host"]), "Property interface.ssid.radius.server.host has invalid format: " + value["host"]); obj.host = value["host"]; } @@ -959,6 +1020,15 @@ function instantiateInterfaceSsidPassPoint(value) { if (exists(value, "venue-url")) { assert(type(value["venue-url"]) == "string", "Property interface.ssid.pass-point.venue-url must be of type string"); + assert((s => { + if (index(s, "data:") == 0) return true; + let m = match(s, /^[a-z+-]+:\/\/([^\/]+).*$/); + if (!m) return false; + if (length(iptoarr(m[1])) != 0) return true; + if (length(m[1]) > 255) return false; + let labels = split(m[1], "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["venue-url"]), "Property interface.ssid.pass-point.venue-url has invalid format: " + value["venue-url"]); obj.venue_url = value["venue-url"]; } @@ -975,6 +1045,15 @@ function instantiateInterfaceSsidPassPoint(value) { if (exists(value, "uri")) { assert(type(value["uri"]) == "string", "Property interface.ssid.pass-point.auth-type.uri must be of type string"); + assert((s => { + if (index(s, "data:") == 0) return true; + let m = match(s, /^[a-z+-]+:\/\/([^\/]+).*$/); + if (!m) return false; + if (length(iptoarr(m[1])) != 0) return true; + if (length(m[1]) > 255) return false; + let labels = split(m[1], "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["uri"]), "Property interface.ssid.pass-point.auth-type.uri has invalid format: " + value["uri"]); obj.uri = value["uri"]; } @@ -987,6 +1066,11 @@ function instantiateInterfaceSsidPassPoint(value) { if (exists(value, "domain-name")) { assert(type(value["domain-name"]) == "string", "Property interface.ssid.pass-point.domain-name must be of type string"); + assert((s => { + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["domain-name"]), "Property interface.ssid.pass-point.domain-name has invalid format: " + value["domain-name"]); obj.domain_name = value["domain-name"]; } @@ -1037,6 +1121,15 @@ function instantiateInterfaceSsidPassPoint(value) { if (exists(value, "uri")) { assert(type(value["uri"]) == "string", "Property interface.ssid.pass-point.icon.item.uri must be of type string"); + assert((s => { + if (index(s, "data:") == 0) return true; + let m = match(s, /^[a-z+-]+:\/\/([^\/]+).*$/); + if (!m) return false; + if (length(iptoarr(m[1])) != 0) return true; + if (length(m[1]) > 255) return false; + let labels = split(m[1], "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["uri"]), "Property interface.ssid.pass-point.icon.item.uri has invalid format: " + value["uri"]); obj.uri = value["uri"]; } @@ -1099,6 +1192,9 @@ function instantiateInterfaceSsid(value) { if (exists(value, "bssid")) { assert(type(value["bssid"]) == "string", "Property interface.ssid.bssid must be of type string"); + assert((s => { + return match(s, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i); + })(value["bssid"]), "Property interface.ssid.bssid has invalid format: " + value["bssid"]); obj.bssid = value["bssid"]; } @@ -1232,6 +1328,10 @@ function instantiateInterfaceTunnelVxlan(value) { if (exists(value, "peer-address")) { assert(type(value["peer-address"]) == "string", "Property interface.tunnel.vxlan.peer-address must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false; + })(value["peer-address"]), "Property interface.tunnel.vxlan.peer-address has invalid format: " + value["peer-address"]); obj.peer_address = value["peer-address"]; } @@ -1258,6 +1358,10 @@ function instantiateInterfaceTunnelGre(value) { if (exists(value, "peer-address")) { assert(type(value["peer-address"]) == "string", "Property interface.tunnel.gre.peer-address must be of type string"); + assert((s => { + let m = match(s, /^(auto|[0-9.]+)\/([0-9]+)$/); + return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false; + })(value["peer-address"]), "Property interface.tunnel.gre.peer-address has invalid format: " + value["peer-address"]); obj.peer_address = value["peer-address"]; } @@ -1464,6 +1568,12 @@ function instantiateServiceNtp(value) { return map(value, (item) => { assert(type(item) == "string", "Items of service.ntp.servers must be of type string"); + assert((s => { + if (length(iptoarr(s)) != 0) return true; + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(item), "Items of service.ntp.servers has invalid format: " + item); return item; }); } @@ -1503,6 +1613,12 @@ function instantiateServiceRtty(value) { if (exists(value, "host")) { assert(type(value["host"]) == "string", "Property service.rtty.host must be of type string"); + assert((s => { + if (length(iptoarr(s)) != 0) return true; + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["host"]), "Property service.rtty.host has invalid format: " + value["host"]); obj.host = value["host"]; } @@ -1534,6 +1650,12 @@ function instantiateServiceLog(value) { if (exists(value, "host")) { assert(type(value["host"]) == "string", "Property service.log.host must be of type string"); + assert((s => { + if (length(iptoarr(s)) != 0) return true; + if (length(s) > 255) return false; + let labels = split(s, "."); + return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0); + })(value["host"]), "Property service.log.host has invalid format: " + value["host"]); obj.host = value["host"]; } diff --git a/ucentral.schema.json b/ucentral.schema.json index 87dc70e..0a2dd70 100644 --- a/ucentral.schema.json +++ b/ucentral.schema.json @@ -383,7 +383,7 @@ }, "gateway": { "type": "string", - "format": "uc-cidr4", + "format": "ipv4", "examples": [ "192.168.1.1" ]