From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 0D706B9AC9 for ; Fri, 15 Mar 2024 12:28:06 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E03221B800 for ; Fri, 15 Mar 2024 12:27:35 +0100 (CET) Received: from druiddev.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP for ; Fri, 15 Mar 2024 12:27:34 +0100 (CET) Received: by druiddev.proxmox.com (Postfix, from userid 1000) id B51088BCF4; Fri, 15 Mar 2024 12:27:34 +0100 (CET) From: Dietmar Maurer To: pbs-devel@lists.proxmox.com Date: Fri, 15 Mar 2024 12:27:31 +0100 Message-Id: <20240315112732.368831-3-dietmar@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240315112732.368831-1-dietmar@proxmox.com> References: <20240315112732.368831-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.547 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [PATCH proxmox 3/4] proxmox-schema: add IP address regex/api-types X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 15 Mar 2024 11:28:06 -0000 Signed-off-by: Dietmar Maurer --- proxmox-schema/src/api_types.rs | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/proxmox-schema/src/api_types.rs b/proxmox-schema/src/api_types.rs index 4e10ebb..381d4cb 100644 --- a/proxmox-schema/src/api_types.rs +++ b/proxmox-schema/src/api_types.rs @@ -3,10 +3,68 @@ use const_format::concatcp; use crate::{ApiStringFormat, Schema, StringSchema}; +#[rustfmt::skip] +const IPV4OCTET: &str = r"(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])"; + +#[rustfmt::skip] +const IPV6H16: &str = r"(?:[0-9a-fA-F]{1,4})"; + +/// Returns the regular expression string to match IPv4 addresses +#[rustfmt::skip] +pub const IPV4RE_STR: &str = concatcp!(r"(?:(?:", IPV4OCTET, r"\.){3}", IPV4OCTET, ")"); + +#[rustfmt::skip] +const IPV6LS32: &str = concatcp!(r"(?:(?:", IPV4RE_STR, "|", IPV6H16, ":", IPV6H16, "))" ); + +/// Returns the regular expression string to match IPv6 addresses +#[rustfmt::skip] +pub const IPV6RE_STR: &str = concatcp!(r"(?:", + r"(?:(?:", r"(?:", IPV6H16, r":){6})", IPV6LS32, r")|", + r"(?:(?:", r"::(?:", IPV6H16, r":){5})", IPV6LS32, r")|", + r"(?:(?:(?:", IPV6H16, r")?::(?:", IPV6H16, r":){4})", IPV6LS32, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,1}", IPV6H16, r")?::(?:", IPV6H16, r":){3})", IPV6LS32, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,2}", IPV6H16, r")?::(?:", IPV6H16, r":){2})", IPV6LS32, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,3}", IPV6H16, r")?::(?:", IPV6H16, r":){1})", IPV6LS32, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,4}", IPV6H16, r")?::", ")", IPV6LS32, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,5}", IPV6H16, r")?::", ")", IPV6H16, r")|", + r"(?:(?:(?:(?:", IPV6H16, r":){0,6}", IPV6H16, r")?::", ")))"); + +/// Returns the regular expression string to match IP addresses (v4 or v6) +#[rustfmt::skip] +pub const IPRE_STR: &str = concatcp!(r"(?:", IPV4RE_STR, "|", IPV6RE_STR, ")"); + +/// Regular expression string to match IP addresses where IPv6 addresses require brackets around +/// them, while for IPv4 they are forbidden. +#[rustfmt::skip] +pub const IPRE_BRACKET_STR: &str = concatcp!(r"(?:", IPV4RE_STR, r"|\[(?:", IPV6RE_STR, r")\]", r")"); + +#[rustfmt::skip] +pub const CIDR_V4_REGEX_STR: &str = concatcp!(r"(?:", IPV4RE_STR, r"/\d{1,2})$"); + +#[rustfmt::skip] +pub const CIDR_V6_REGEX_STR: &str = concatcp!(r"(?:", IPV6RE_STR, r"/\d{1,3})$"); + #[rustfmt::skip] const SAFE_ID_REGEX_STR: &str = r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)"; const_regex! { + /// IPv4 regular expression. + pub IP_V4_REGEX = concatcp!(r"^", IPV4RE_STR, r"$"); + + /// IPv6 regular expression. + pub IP_V6_REGEX = concatcp!(r"^", IPV6RE_STR, r"$"); + + /// Regex to match IP addresses (V4 or V6) + pub IP_REGEX = concatcp!(r"^", IPRE_STR, r"$"); + + /// Regex to match IP addresses where IPv6 addresses require brackets around + /// them, while for IPv4 they are forbidden. + pub IP_BRACKET_REGEX = concatcp!(r"^", IPRE_BRACKET_STR, r"$"); + + pub CIDR_V4_REGEX = concatcp!(r"^", CIDR_V4_REGEX_STR, r"$"); + pub CIDR_V6_REGEX = concatcp!(r"^", CIDR_V6_REGEX_STR, r"$"); + pub CIDR_REGEX = concatcp!(r"^(?:", CIDR_V4_REGEX_STR, "|", CIDR_V6_REGEX_STR, r")$"); + /// Regex for safe identifiers. /// /// This @@ -36,3 +94,29 @@ pub const COMMENT_SCHEMA: Schema = StringSchema::new("Comment.") .format(&SINGLE_LINE_COMMENT_FORMAT) .max_length(128) .schema(); + +#[test] +fn test_regexes() { + assert!(IP_REGEX.is_match("127.0.0.1")); + assert!(IP_V4_REGEX.is_match("127.0.0.1")); + assert!(!IP_V6_REGEX.is_match("127.0.0.1")); + + assert!(CIDR_V4_REGEX.is_match("127.0.0.1/24")); + assert!(CIDR_REGEX.is_match("127.0.0.1/24")); + + assert!(IP_REGEX.is_match("::1")); + assert!(IP_REGEX.is_match("2014:b3a::27")); + assert!(IP_REGEX.is_match("2014:b3a::192.168.0.1")); + assert!(IP_REGEX.is_match("2014:b3a:0102:adf1:1234:4321:4afA:BCDF")); + assert!(!IP_V4_REGEX.is_match("2014:b3a:0102:adf1:1234:4321:4afA:BCDF")); + assert!(IP_V6_REGEX.is_match("2014:b3a:0102:adf1:1234:4321:4afA:BCDF")); + + assert!(CIDR_V6_REGEX.is_match("2014:b3a:0102:adf1:1234:4321:4afA:BCDF/60")); + assert!(CIDR_REGEX.is_match("2014:b3a:0102:adf1:1234:4321:4afA:BCDF/60")); + + assert!(IP_BRACKET_REGEX.is_match("127.0.0.1")); + assert!(IP_BRACKET_REGEX.is_match("[::1]")); + assert!(IP_BRACKET_REGEX.is_match("[2014:b3a::27]")); + assert!(IP_BRACKET_REGEX.is_match("[2014:b3a::192.168.0.1]")); + assert!(IP_BRACKET_REGEX.is_match("[2014:b3a:0102:adf1:1234:4321:4afA:BCDF]")); +} -- 2.39.2