From 0b8834d04a7b7eb226b6e0c1ac8a3cfa5a2363b7 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:55:11 +0200 Subject: [PATCH] Bootstrap: Accept RFC 6761 reserved TLDs during bootstrap --- CHANGELOG.md | 1 + crates/jmap/src/registry/mapping/bootstrap.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d4a905..3782536e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This version includes **multiple breaking changes**. If you are upgrading from v - Sorting order issues when emails lack `receivedAt` headers. - IMAP: Fix `BINARY` fetch responses (#2940). - ACME: Allow requesting apex domain certificates. +- Bootstrap: Accept RFC 6761 reserved TLDs during bootstrap. - Reverse proxy issues. - OSS builds. - DNS Updater: diff --git a/crates/jmap/src/registry/mapping/bootstrap.rs b/crates/jmap/src/registry/mapping/bootstrap.rs index c5a7b257..44636157 100644 --- a/crates/jmap/src/registry/mapping/bootstrap.rs +++ b/crates/jmap/src/registry/mapping/bootstrap.rs @@ -121,7 +121,7 @@ pub(crate) async fn bootstrap_set( // Validate domain name and hostname let server_hostname = bootstrap.server_hostname.trim().to_lowercase(); let domain_name = bootstrap.default_domain.trim().to_lowercase(); - if psl::domain_str(&server_hostname).is_none() && !server_hostname.ends_with(".test") { + if !is_valid_domain(&server_hostname) { set.response.not_updated.append( id, SetError::invalid_properties() @@ -130,7 +130,7 @@ pub(crate) async fn bootstrap_set( ); break; } - if psl::domain_str(&domain_name).is_none() && !domain_name.ends_with(".test") { + if !is_valid_domain(&domain_name) { set.response.not_updated.append( id, SetError::invalid_properties() @@ -505,6 +505,15 @@ pub(crate) async fn bootstrap_set( Ok(set) } +fn is_valid_domain(hostname: &str) -> bool { + const RESERVED_TLDS: &[&str] = &["test", "localhost", "local"]; + psl::domain_str(hostname).is_some() + || RESERVED_TLDS.contains(&hostname) + || hostname + .rsplit_once('.') + .is_some_and(|(_, tld)| RESERVED_TLDS.contains(&tld)) +} + async fn write_object(registry: &RegistryStore, object: &Object) -> Result> { match registry.write(RegistryWrite::insert(object)).await { Ok(RegistryWriteResult::Success(id)) => Ok(id),