From: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
To: pdm-devel@lists.proxmox.com
Cc: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
Subject: [PATCH proxmox 3/4] api-types: add a scheme generic URL regex
Date: Thu, 13 Aug 2026 17:05:50 +0200 [thread overview]
Message-ID: <20260813150551.415237-4-t.ellmenreich@proxmox.com> (raw)
In-Reply-To: <20260813150551.415237-1-t.ellmenreich@proxmox.com>
The current URL regex, HTTP_URL_REGEX, is specific to the HTTP and HTTPS
schemes. This patch creates a new regex that is scheme-generic and thus
allows any other valid scheme.
It also adds tests that cover some basic URL edge cases.
Signed-off-by: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
---
pbs-api-types/src/lib.rs | 1 +
proxmox-schema/src/api_types.rs | 40 ++++++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs
index 21ef733d..924f356b 100644
--- a/pbs-api-types/src/lib.rs
+++ b/pbs-api-types/src/lib.rs
@@ -44,6 +44,7 @@ pub use proxmox_schema::api_types::HTTP_URL_SCHEMA;
pub use proxmox_schema::api_types::MULTI_LINE_COMMENT_SCHEMA;
pub use proxmox_schema::api_types::NODE_SCHEMA;
pub use proxmox_schema::api_types::SINGLE_LINE_COMMENT_FORMAT;
+pub use proxmox_schema::api_types::URL_SCHEMA;
pub use proxmox_schema::api_types::{
BLOCKDEVICE_DISK_AND_PARTITION_NAME_SCHEMA, BLOCKDEVICE_NAME_SCHEMA,
};
diff --git a/proxmox-schema/src/api_types.rs b/proxmox-schema/src/api_types.rs
index 81878dbe..05e50f26 100644
--- a/proxmox-schema/src/api_types.rs
+++ b/proxmox-schema/src/api_types.rs
@@ -50,6 +50,10 @@ pub const CIDR_V6_REGEX_STR: &str = concatcp!(r"(?:", IPV6RE_STR, r"/\d{1,3})$")
#[rustfmt::skip]
pub const SAFE_ID_REGEX_STR: &str = r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)";
+/// Regular expression string to match allowed URL schemes.
+#[rustfmt::skip]
+pub const URL_SCHEME_REGEX_STR: &str = r"(?:[a-zA-Z][a-zA-Z0-9\+.\-]*)";
+
/// Regular expression string to match a host, optional port and optionally a
/// path, although the path remains completely unvalidated except a few
/// prohibited characters.
@@ -113,6 +117,12 @@ const_regex! {
pub DNS_NAME_OR_IP_REGEX = concatcp!(r"^(?:", DNS_NAME_STR, "|", IPRE_STR, r")$");
pub HOST_PORT_REGEX = concatcp!(r"^(?:", DNS_NAME_STR, "|", IPRE_BRACKET_STR, "):", PORT_REGEX_STR ,"$");
+ /// Matches URL constructed out of a scheme, host, port, and path. With
+ /// the host possibly being an IPv[4|6] address and the port being
+ /// optional. The path remains completely unvalidated except for a few
+ /// prohibited characters.
+ pub URL_REGEX = concatcp!(r"^", URL_SCHEME_REGEX_STR, "://", URL_HOST_PORT_PATH_REGEX_STR, "$");
+
/// A specialisation of [`URL_REGEX`] regex that only allows http and
/// https as the URL scheme
pub HTTP_URL_REGEX = concatcp!(r"^https?://", URL_HOST_PORT_PATH_REGEX_STR, "$");
@@ -166,6 +176,7 @@ pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
pub const HOSTNAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&HOSTNAME_REGEX);
pub const HOST_PORT_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&HOST_PORT_REGEX);
pub const HTTP_URL_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&HTTP_URL_REGEX);
+pub const URL_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&URL_REGEX);
pub const DNS_ALIAS_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&DNS_ALIAS_REGEX);
pub const DNS_NAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&DNS_NAME_REGEX);
@@ -243,6 +254,10 @@ pub const PORT_SCHEMA: Schema = IntegerSchema::new("Node port")
.maximum(65535)
.schema();
+pub const URL_SCHEMA: Schema = StringSchema::new("Url with optional port.")
+ .format(&URL_FORMAT)
+ .schema();
+
pub const HTTP_URL_SCHEMA: Schema = StringSchema::new("HTTP(s) url with optional port.")
.format(&HTTP_URL_FORMAT)
.schema();
@@ -286,7 +301,7 @@ pub const DISK_LIST_SCHEMA: Schema = StringSchema::new("A list of disk names, co
mod tests {
use super::{
CIDR_REGEX, CIDR_V4_REGEX, CIDR_V6_REGEX, ED25519_BASE64_KEY_REGEX, IP_BRACKET_REGEX,
- IP_REGEX, IP_V4_REGEX, IP_V6_REGEX,
+ IP_REGEX, IP_V4_REGEX, IP_V6_REGEX, URL_REGEX,
};
#[test]
@@ -327,4 +342,27 @@ mod tests {
// 33 bytes of data
assert!(!ED25519_BASE64_KEY_REGEX.is_match("IiC3Nkh4Fn2ukUZUNmdK5K5CWO53Zmk/eGlKO4m6aCD/"));
}
+
+ #[test]
+ fn test_url_regex_on_valid_urls() {
+ assert!(URL_REGEX.is_match("https://www.example.com/index.html"));
+ assert!(URL_REGEX.is_match("https://[2001:db8::1]/docs/%E2%9C%93.html?lang=en"));
+ assert!(URL_REGEX.is_match("ftp://ftp.example.com/pub/archive/file.zip"));
+ assert!(URL_REGEX.is_match("file://test.com/home/alice/documents/report.pdf"));
+ assert!(URL_REGEX.is_match("https://example.com:443/a%20file.html?name=John%20Doe#top"));
+ }
+
+ #[test]
+ fn test_url_regex_on_invalid_urls() {
+ // is missing the ':' after the scheme
+ assert!(!URL_REGEX.is_match("https//www.example.com/index.html"));
+ // has two port numbers
+ assert!(!URL_REGEX.is_match("http://example.com:80:90/page"));
+ // only has one '/' after the scheme
+ assert!(!URL_REGEX.is_match("ftp:/ftp.example.com/file.txt"));
+ // has userinfo (technically correct, but not accepted by the regex)
+ assert!(!URL_REGEX.is_match("mailto:alice@example.com"));
+ // missing closing angle ']' brachet
+ assert!(!URL_REGEX.is_match("https://[2001:db8::1/path"));
+ }
}
--
2.47.3
next prev parent reply other threads:[~2026-08-13 15:06 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 15:05 [PATCH datacenter-manager/proxmox 0/4] fix #7747: OpenID: allow non HTTP scheme in redirect URL Thomas Ellmenreich
2026-08-13 15:05 ` [PATCH proxmox 1/4] api-types: reorganise unit tests Thomas Ellmenreich
2026-08-13 15:05 ` [PATCH proxmox 2/4] api-types: refactor HTTP_URL_REGEX construction Thomas Ellmenreich
2026-08-13 15:05 ` Thomas Ellmenreich [this message]
2026-08-13 15:05 ` [PATCH datacenter-manager 4/4] fix #7747: openid: allow non HTTP schemes in redirect URL Thomas Ellmenreich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260813150551.415237-4-t.ellmenreich@proxmox.com \
--to=t.ellmenreich@proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.