Fix input validation for fields in monitor edit form

This commit is contained in:
ekrekeler 2025-02-26 00:18:47 -06:00
parent aaf438a248
commit 21f629e055
No known key found for this signature in database
GPG key ID: 4C66C864B6B00854
2 changed files with 55 additions and 24 deletions

View file

@ -290,9 +290,9 @@
type="text" type="text"
class="form-control" class="form-control"
:pattern="`${ :pattern="`${
monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern.source :
monitor.type === 'dns' ? ipOrDnsNameRegexPattern : monitor.type === 'dns' ? ipOrDnsNameRegexPattern.source :
ipOrHostnameRegexPattern ipOrHostnameRegexPattern.source
}`" }`"
required required
data-testid="hostname-input" data-testid="hostname-input"
@ -371,7 +371,7 @@
<template v-if="monitor.type === 'dns'"> <template v-if="monitor.type === 'dns'">
<div class="my-3"> <div class="my-3">
<label for="dns_resolve_server" class="form-label">{{ $t("Resolver Server") }}</label> <label for="dns_resolve_server" class="form-label">{{ $t("Resolver Server") }}</label>
<input id="dns_resolve_server" v-model="monitor.dns_resolve_server" type="text" class="form-control" :pattern="ipOrHostnameRegex" required> <input id="dns_resolve_server" v-model="monitor.dns_resolve_server" type="text" class="form-control" :pattern="dnsResolverRegex" required>
<div class="form-text"> <div class="form-text">
{{ $t("resolverserverDescription") }} {{ $t("resolverserverDescription") }}
</div> </div>
@ -384,7 +384,7 @@
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}"> <input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
</div> </div>
<div class="form-text"> <div class="form-text">
{{ $t("dohQueryPathDescription") }}{{ ' "{query}".' }} {{ $t("dohQueryPathDescription") + " " }}{{ '"{query}".' }}
</div> </div>
</div> </div>
@ -1179,7 +1179,8 @@ export default {
dnsresolvetypeOptions: [], dnsresolvetypeOptions: [],
dnsTransportOptions: [], dnsTransportOptions: [],
kafkaSaslMechanismOptions: [], kafkaSaslMechanismOptions: [],
ipRegexPattern: hostNameRegexPattern().split("|")[0], ipRegexPattern: hostNameRegexPattern(false, true, false),
hostnameRegexPattern: hostNameRegexPattern(false, false, true),
ipOrHostnameRegexPattern: hostNameRegexPattern(), ipOrHostnameRegexPattern: hostNameRegexPattern(),
mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true), mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true),
ipOrDnsNameRegexPattern: dnsNameRegexPattern(), ipOrDnsNameRegexPattern: dnsNameRegexPattern(),
@ -1202,7 +1203,7 @@ export default {
// Allow to test with simple dns server with port (127.0.0.1:5300) // Allow to test with simple dns server with port (127.0.0.1:5300)
if (! isDev) { if (! isDev) {
return this.ipRegexPattern; return this.ipRegexPattern.source;
} }
return null; return null;
}, },
@ -1211,16 +1212,24 @@ export default {
// Permit either IP address or hostname (127.0.0.1, dns.example.com) // Permit either IP address or hostname (127.0.0.1, dns.example.com)
if (! isDev) { if (! isDev) {
return this.ipOrHostnameRegexPattern; return this.ipOrHostnameRegexPattern.source;
} }
return null; return null;
}, },
dnsNameRegex() { dnsResolverRegex() {
// Permit IP address, hostname, TLD, or root // Permit IP address for TCP/UDP resolvers, hostname for DoH/DoT
if (! isDev) { if (! isDev) {
return this.ipOrDnsNameRegexPattern; switch (this.monitor.dns_transport) {
case "UDP":
case "TCP":
return this.ipRegexPattern.source;
case "DoH":
case "DoT":
return this.hostnameRegexPattern.source;
}
} }
return null; return null;
}, },
@ -1229,7 +1238,7 @@ export default {
// Permit only URL paths with a query parameter ( {query} ) // Permit only URL paths with a query parameter ( {query} )
if (! isDev) { if (! isDev) {
return this.queryRegexPattern; return this.queryRegexPattern.source;
} }
return null; return null;
}, },

View file

@ -112,17 +112,37 @@ export function getDevContainerServerHostname() {
* Regex pattern for identifying hostnames and IP addresses * Regex pattern for identifying hostnames and IP addresses
* @param {boolean} mqtt whether or not the regex should take into * @param {boolean} mqtt whether or not the regex should take into
* account the fact that it is an mqtt uri * account the fact that it is an mqtt uri
* @param {boolean} ip whether the regex should match IP addresses
* @param {boolean} hostname whether the regex should match hostnames
* @returns {RegExp} The requested regex * @returns {RegExp} The requested regex
*/ */
export function hostNameRegexPattern(mqtt = false) { export function hostNameRegexPattern(mqtt = false, ip = true, hostname = true) {
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect) // mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?"; const mqttSchemeRegexPattern = /((mqtt|ws)s?:\/\/)?/;
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/ // Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
const ipRegexPattern = `((^${mqtt ? mqttSchemeRegexPattern : ""}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$))`; const ipv4RegexPattern = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address const ipv6RegexPattern = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
const hostNameRegexPattern = `^${mqtt ? mqttSchemeRegexPattern : ""}([a-zA-Z0-9])?(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])(\\.)?$`; // See this answer for detailed a explanation: https://stackoverflow.com/a/53875771/1854468
/* eslint-disable-next-line no-useless-escape */
const hostNameRegexPattern = /^[a-zA-Z][a-zA-Z0-9\-]{0,62}\.([a-zA-Z0-9][a-zA-Z0-9\-]{0,62}\.)*([a-zA-Z]{2,63}|[xX][nN]--[a-zA-Z0-9]{0,59})$/;
/* eslint-disable-next-line no-useless-escape */
const localNameRegexPattern = /^[a-zA-Z0-9][a-zA-Z0-9\-]{0,127}$/;
return `${ipRegexPattern}|${hostNameRegexPattern}`; let patterns = [];
if (ip) {
patterns.push(ipv4RegexPattern, ipv6RegexPattern);
}
if (hostname) {
patterns.push(hostNameRegexPattern, localNameRegexPattern);
}
if (mqtt) {
// all modified patterns must start with "^" for this to work
patterns = patterns.map(pattern => {
return new RegExp(`^${mqttSchemeRegexPattern.source}${pattern.source.slice(1)}`);
});
}
return new RegExp(patterns.map(pattern => `(${pattern.source})`).join("|"));
} }
/** /**
@ -131,12 +151,13 @@ export function hostNameRegexPattern(mqtt = false) {
*/ */
export function dnsNameRegexPattern() { export function dnsNameRegexPattern() {
// This borrows ipRegexPattern from hostNameRegexPattern above // This borrows ipRegexPattern from hostNameRegexPattern above
const ipRegexPattern = hostNameRegexPattern().split("|")[0]; const ipRegexPattern = hostNameRegexPattern(false, true, false);
// Similar to hostNameRegexPattern, except the hostname pattern // Similar to hostNameRegexPattern, except the hostname pattern
// can also match root (.) and top-level domains (.com, .org) // can also match root (.) and top-level domains (.com, .org),
const dnsNamePattern = "^(\\.|(\\.?[a-zA-Z0-9_]+)+)$"; // and may contain underscores (_)
const dnsNamePattern = /^(\.|(\.?[a-zA-Z0-9\-_]+)+)$/;
return `${ipRegexPattern}|${dnsNamePattern}`; return new RegExp(`(${ipRegexPattern.source})|(${dnsNamePattern.source})`);
} }
/** /**
@ -146,9 +167,10 @@ export function dnsNameRegexPattern() {
*/ */
export function urlPathRegexPattern(qstr = false) { export function urlPathRegexPattern(qstr = false) {
// Ensures a URL path follows query string format // Ensures a URL path follows query string format
const queryStringRegexPattern = "^/?(([a-zA-Z0-9\\-_%])+/)*[a-zA-Z0-9\\-_%]*\\?([a-zA-Z0-9\\-_%]+=[a-zA-Z0-9\\-_%]*&?)+$"; const queryStringRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
// Only checks for valid URL path containing "{query}" // Only checks for valid URL path containing "{query}"
const queryRegexPattern = "^[a-zA-Z0-9\\-._~:\\/?#\\[\\]@!$&'\\(\\)*+,;=]*\\{query\\}[a-zA-Z0-9\\-._~:\\/?#\\[\\]@!$&'\\(\\)*+,;=]*$"; /* eslint-disable-next-line no-useless-escape */
const queryRegexPattern = /^[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*\{query\}[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*$/;
if (qstr) { if (qstr) {
return queryStringRegexPattern; return queryStringRegexPattern;