@@ -16,27 +16,38 @@ use regex::Regex;
1616use snafu:: Snafu ;
1717
1818/// Minimal length required by RFC 1123 is 63. Up to 255 allowed, unsupported by k8s.
19- const RFC_1123_LABEL_MAX_LENGTH : usize = 63 ;
20- pub const RFC_1123_LABEL_FMT : & str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?" ;
21- const RFC_1123_LABEL_ERROR_MSG : & str = "a RFC 1123 label must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" ;
19+ pub const RFC_1123_LABEL_MAX_LENGTH : usize = 63 ;
20+ // This is a modified RFC 1123 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
21+ pub const LOWERCASE_RFC_1123_LABEL_FMT : & str = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" ;
22+ const LOWERCASE_RFC_1123_LABEL_ERROR_MSG : & str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" ;
2223
23- /// This is a subdomain's max length in DNS (RFC 1123)
24- const RFC_1123_SUBDOMAIN_MAX_LENGTH : usize = 253 ;
25- const RFC_1123_SUBDOMAIN_FMT : & str =
26- concatcp ! ( RFC_1123_LABEL_FMT , "(\\ ." , RFC_1123_LABEL_FMT , ")*" ) ;
24+ // This is a RFC 1123 format, see https://www.rfc-editor.org/rfc/rfc1123
25+ const RFC_1123_LABEL_FMT : & str = "[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?" ;
2726
28- const DOMAIN_MAX_LENGTH : usize = RFC_1123_SUBDOMAIN_MAX_LENGTH ;
29- /// Same as [`RFC_1123_SUBDOMAIN_FMT`], but allows a trailing dot
30- const DOMAIN_FMT : & str = concatcp ! ( RFC_1123_SUBDOMAIN_FMT , "\\ .?" ) ;
27+ /// This is a subdomain's max length in DNS (RFC 1123)
28+ pub const RFC_1123_SUBDOMAIN_MAX_LENGTH : usize = 253 ;
29+ pub const LOWERCASE_RFC_1123_SUBDOMAIN_FMT : & str = concatcp ! (
30+ LOWERCASE_RFC_1123_LABEL_FMT ,
31+ "(\\ ." ,
32+ LOWERCASE_RFC_1123_LABEL_FMT ,
33+ ")*"
34+ ) ;
35+ const LOWERCASE_RFC_1123_SUBDOMAIN_ERROR_MSG : & str = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" ;
36+
37+ pub const DOMAIN_MAX_LENGTH : usize = RFC_1123_SUBDOMAIN_MAX_LENGTH ;
38+
39+ /// String of one or multiple [`RFC_1123_LABEL_FMT`] separated by dots but also allowing a trailing dot
40+ const DOMAIN_FMT : & str = concatcp ! ( RFC_1123_LABEL_FMT , "(\\ ." , RFC_1123_LABEL_FMT , ")*\\ .?" ) ;
3141const DOMAIN_ERROR_MSG : & str = "a domain must consist of alphanumeric characters, '-' or '.', and must start with an alphanumeric character and end with an alphanumeric character or '.'" ;
3242
3343// FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter
3444// (and not a number).
35- const RFC_1035_LABEL_FMT : & str = "[a-z]([-a-z0-9]*[a-z0-9])?" ;
36- const RFC_1035_LABEL_ERROR_MSG : & str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character" ;
45+ // This is a modified RFC 1035 format according to the Kubernetes specification, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names
46+ pub const LOWERCASE_RFC_1035_LABEL_FMT : & str = "[a-z]([-a-z0-9]*[a-z0-9])?" ;
47+ const LOWERCASE_RFC_1035_LABEL_ERROR_MSG : & str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character" ;
3748
3849// This is a label's max length in DNS (RFC 1035)
39- const RFC_1035_LABEL_MAX_LENGTH : usize = 63 ;
50+ pub const RFC_1035_LABEL_MAX_LENGTH : usize = 63 ;
4051
4152// Technically Kerberos allows more realm names
4253// (https://web.mit.edu/kerberos/krb5-1.21/doc/admin/realm_config.html#realm-name),
@@ -54,12 +65,19 @@ pub(crate) static DOMAIN_REGEX: LazyLock<Regex> = LazyLock::new(|| {
5465 Regex :: new ( & format ! ( "^{DOMAIN_FMT}$" ) ) . expect ( "failed to compile domain regex" )
5566} ) ;
5667
57- static RFC_1123_LABEL_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
58- Regex :: new ( & format ! ( "^{RFC_1123_LABEL_FMT}$" ) ) . expect ( "failed to compile RFC 1123 label regex" )
68+ static LOWERCASE_RFC_1123_LABEL_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
69+ Regex :: new ( & format ! ( "^{LOWERCASE_RFC_1123_LABEL_FMT}$" ) )
70+ . expect ( "failed to compile RFC 1123 label regex" )
71+ } ) ;
72+
73+ static LOWERCASE_RFC_1123_SUBDOMAIN_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
74+ Regex :: new ( & format ! ( "^{LOWERCASE_RFC_1123_SUBDOMAIN_FMT}$" ) )
75+ . expect ( "failed to compile RFC 1123 subdomain regex" )
5976} ) ;
6077
61- static RFC_1035_LABEL_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
62- Regex :: new ( & format ! ( "^{RFC_1035_LABEL_FMT}$" ) ) . expect ( "failed to compile RFC 1035 label regex" )
78+ static LOWERCASE_RFC_1035_LABEL_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
79+ Regex :: new ( & format ! ( "^{LOWERCASE_RFC_1035_LABEL_FMT}$" ) )
80+ . expect ( "failed to compile RFC 1035 label regex" )
6381} ) ;
6482
6583pub ( crate ) static KERBEROS_REALM_NAME_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
@@ -198,28 +216,44 @@ pub fn is_domain(value: &str) -> Result {
198216 ] )
199217}
200218
201- /// Tests for a string that conforms to the definition of a label in DNS (RFC 1123).
219+ /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1123)
220+ /// used in Namespace names, see: [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)
202221/// Maximum label length supported by k8s is 63 characters (minimum required).
203- pub fn is_rfc_1123_label ( value : & str ) -> Result {
222+ pub fn is_lowercase_rfc_1123_label ( value : & str ) -> Result {
204223 validate_all ( [
205224 validate_str_length ( value, RFC_1123_LABEL_MAX_LENGTH ) ,
206225 validate_str_regex (
207226 value,
208- & RFC_1123_LABEL_REGEX ,
209- RFC_1123_LABEL_ERROR_MSG ,
227+ & LOWERCASE_RFC_1123_LABEL_REGEX ,
228+ LOWERCASE_RFC_1123_LABEL_ERROR_MSG ,
210229 & [ "example-label" , "1-label-1" ] ,
211230 ) ,
212231 ] )
213232}
214233
215- /// Tests for a string that conforms to the definition of a label in DNS (RFC 1035).
216- pub fn is_rfc_1035_label ( value : & str ) -> Result {
234+ /// Tests for a string that conforms to the kubernetes-specific definition of a subdomain in DNS (RFC 1123)
235+ /// used in ConfigMap names, see [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
236+ pub fn is_lowercase_rfc_1123_subdomain ( value : & str ) -> Result {
237+ validate_all ( [
238+ validate_str_length ( value, RFC_1123_SUBDOMAIN_MAX_LENGTH ) ,
239+ validate_str_regex (
240+ value,
241+ & LOWERCASE_RFC_1123_SUBDOMAIN_REGEX ,
242+ LOWERCASE_RFC_1123_SUBDOMAIN_ERROR_MSG ,
243+ & [ "example.com" ] ,
244+ ) ,
245+ ] )
246+ }
247+
248+ /// Tests for a string that conforms to the kubernetes-specific definition of a label in DNS (RFC 1035)
249+ /// used in Service names, see: [Kubernetes Docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names)
250+ pub fn is_lowercase_rfc_1035_label ( value : & str ) -> Result {
217251 validate_all ( [
218252 validate_str_length ( value, RFC_1035_LABEL_MAX_LENGTH ) ,
219253 validate_str_regex (
220254 value,
221- & RFC_1035_LABEL_REGEX ,
222- RFC_1035_LABEL_ERROR_MSG ,
255+ & LOWERCASE_RFC_1035_LABEL_REGEX ,
256+ LOWERCASE_RFC_1035_LABEL_ERROR_MSG ,
223257 & [ "my-name" , "abc-123" ] ,
224258 ) ,
225259 ] )
@@ -261,7 +295,7 @@ pub fn name_is_dns_label(name: &str, prefix: bool) -> Result {
261295 name = mask_trailing_dash ( name) ;
262296 }
263297
264- is_rfc_1035_label ( & name)
298+ is_lowercase_rfc_1035_label ( & name)
265299}
266300
267301/// Validates a namespace name.
@@ -277,28 +311,14 @@ mod tests {
277311
278312 use super :: * ;
279313
280- const RFC_1123_SUBDOMAIN_ERROR_MSG : & str = "a RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" ;
281-
282- static RFC_1123_SUBDOMAIN_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
283- Regex :: new ( & format ! ( "^{RFC_1123_SUBDOMAIN_FMT}$" ) )
284- . expect ( "failed to compile RFC 1123 subdomain regex" )
285- } ) ;
286-
287- /// Tests for a string that conforms to the definition of a subdomain in DNS (RFC 1123).
288- fn is_rfc_1123_subdomain ( value : & str ) -> Result {
289- validate_all ( [
290- validate_str_length ( value, RFC_1123_SUBDOMAIN_MAX_LENGTH ) ,
291- validate_str_regex (
292- value,
293- & RFC_1123_SUBDOMAIN_REGEX ,
294- RFC_1123_SUBDOMAIN_ERROR_MSG ,
295- & [ "example.com" ] ,
296- ) ,
297- ] )
298- }
299-
300314 #[ rstest]
301315 #[ case( "" ) ]
316+ #[ case( "A" ) ]
317+ #[ case( "aBc" ) ]
318+ #[ case( "ABC" ) ]
319+ #[ case( "A1" ) ]
320+ #[ case( "A-1" ) ]
321+ #[ case( "1-A" ) ]
302322 #[ case( "-" ) ]
303323 #[ case( "a-" ) ]
304324 #[ case( "-a" ) ]
@@ -325,6 +345,24 @@ mod tests {
325345 #[ case( "1 " ) ]
326346 #[ case( " 1" ) ]
327347 #[ case( "1 2" ) ]
348+ #[ case( "A.a" ) ]
349+ #[ case( "aB.a" ) ]
350+ #[ case( "ab.A" ) ]
351+ #[ case( "A1.a" ) ]
352+ #[ case( "a1.A" ) ]
353+ #[ case( "A.1" ) ]
354+ #[ case( "aB.1" ) ]
355+ #[ case( "A1.1" ) ]
356+ #[ case( "0.A" ) ]
357+ #[ case( "01.A" ) ]
358+ #[ case( "012.A" ) ]
359+ #[ case( "1A.a" ) ]
360+ #[ case( "1a.A" ) ]
361+ #[ case( "1A.1" ) ]
362+ #[ case( "a.B.c.d.e" ) ]
363+ #[ case( "A.B.C.D.E" ) ]
364+ #[ case( "aa.bB.cc.dd.ee" ) ]
365+ #[ case( "AA.BB.CC.DD.EE" ) ]
328366 #[ case( "a@b" ) ]
329367 #[ case( "a,b" ) ]
330368 #[ case( "a_b" ) ]
@@ -335,77 +373,53 @@ mod tests {
335373 #[ case( "a$b" ) ]
336374 #[ case( & "a" . repeat( 254 ) ) ]
337375 fn is_rfc_1123_subdomain_fail ( #[ case] value : & str ) {
338- assert ! ( is_rfc_1123_subdomain ( value) . is_err( ) ) ;
376+ assert ! ( is_lowercase_rfc_1123_subdomain ( value) . is_err( ) ) ;
339377 }
340378
341379 #[ rstest]
342380 #[ case( "a" ) ]
343- #[ case( "A" ) ]
344381 #[ case( "ab" ) ]
345382 #[ case( "abc" ) ]
346- #[ case( "aBc" ) ]
347- #[ case( "ABC" ) ]
348383 #[ case( "a1" ) ]
349- #[ case( "A1" ) ]
350384 #[ case( "a-1" ) ]
351- #[ case( "A-1" ) ]
352385 #[ case( "a--1--2--b" ) ]
353386 #[ case( "0" ) ]
354387 #[ case( "01" ) ]
355388 #[ case( "012" ) ]
356389 #[ case( "1a" ) ]
357390 #[ case( "1-a" ) ]
358- #[ case( "1-A" ) ]
359391 #[ case( "1--a--b--2" ) ]
360392 #[ case( "a.a" ) ]
361- #[ case( "A.a" ) ]
362393 #[ case( "ab.a" ) ]
363- #[ case( "aB.a" ) ]
364- #[ case( "ab.A" ) ]
365394 #[ case( "abc.a" ) ]
366395 #[ case( "a1.a" ) ]
367- #[ case( "A1.a" ) ]
368- #[ case( "a1.A" ) ]
369396 #[ case( "a-1.a" ) ]
370397 #[ case( "a--1--2--b.a" ) ]
371398 #[ case( "a.1" ) ]
372- #[ case( "A.1" ) ]
373399 #[ case( "ab.1" ) ]
374- #[ case( "aB.1" ) ]
375400 #[ case( "abc.1" ) ]
376401 #[ case( "a1.1" ) ]
377- #[ case( "A1.1" ) ]
378402 #[ case( "a-1.1" ) ]
379403 #[ case( "a--1--2--b.1" ) ]
380404 #[ case( "0.a" ) ]
381- #[ case( "0.A" ) ]
382405 #[ case( "01.a" ) ]
383- #[ case( "01.A" ) ]
384406 #[ case( "012.a" ) ]
385- #[ case( "012.A" ) ]
386407 #[ case( "1a.a" ) ]
387- #[ case( "1A.a" ) ]
388- #[ case( "1a.A" ) ]
389408 #[ case( "1-a.a" ) ]
390409 #[ case( "1--a--b--2" ) ]
391410 #[ case( "0.1" ) ]
392411 #[ case( "01.1" ) ]
393412 #[ case( "012.1" ) ]
394413 #[ case( "1a.1" ) ]
395- #[ case( "1A.1" ) ]
396414 #[ case( "1-a.1" ) ]
397415 #[ case( "1--a--b--2.1" ) ]
398416 #[ case( "a.b.c.d.e" ) ]
399- #[ case( "a.B.c.d.e" ) ]
400- #[ case( "A.B.C.D.E" ) ]
401417 #[ case( "aa.bb.cc.dd.ee" ) ]
402- #[ case( "aa.bB.cc.dd.ee" ) ]
403- #[ case( "AA.BB.CC.DD.EE" ) ]
404418 #[ case( "1.2.3.4.5" ) ]
405419 #[ case( "11.22.33.44.55" ) ]
406420 #[ case( & "a" . repeat( 253 ) ) ]
407421 fn is_rfc_1123_subdomain_pass ( #[ case] value : & str ) {
408- assert ! ( is_rfc_1123_subdomain ( value) . is_ok( ) ) ;
422+ assert ! ( is_lowercase_rfc_1123_subdomain ( value) . is_ok( ) ) ;
409423 // Every valid RFC1123 is also a valid domain
410424 assert ! ( is_domain( value) . is_ok( ) ) ;
411425 }
@@ -469,7 +483,7 @@ mod tests {
469483 #[ case( "1 2" ) ]
470484 #[ case( & "a" . repeat( 64 ) ) ]
471485 fn is_rfc_1035_label_fail ( #[ case] value : & str ) {
472- assert ! ( is_rfc_1035_label ( value) . is_err( ) ) ;
486+ assert ! ( is_lowercase_rfc_1035_label ( value) . is_err( ) ) ;
473487 }
474488
475489 #[ rstest]
@@ -481,6 +495,6 @@ mod tests {
481495 #[ case( "a--1--2--b" ) ]
482496 #[ case( & "a" . repeat( 63 ) ) ]
483497 fn is_rfc_1035_label_pass ( #[ case] value : & str ) {
484- assert ! ( is_rfc_1035_label ( value) . is_ok( ) ) ;
498+ assert ! ( is_lowercase_rfc_1035_label ( value) . is_ok( ) ) ;
485499 }
486500}
0 commit comments