@@ -16,27 +16,38 @@ use regex::Regex;
16
16
use snafu:: Snafu ;
17
17
18
18
/// 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" ;
22
23
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])?" ;
27
26
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 , ")*\\ .?" ) ;
31
41
const 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 '.'" ;
32
42
33
43
// FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter
34
44
// (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" ;
37
48
38
49
// 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 ;
40
51
41
52
// Technically Kerberos allows more realm names
42
53
// (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(|| {
54
65
Regex :: new ( & format ! ( "^{DOMAIN_FMT}$" ) ) . expect ( "failed to compile domain regex" )
55
66
} ) ;
56
67
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" )
59
76
} ) ;
60
77
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" )
63
81
} ) ;
64
82
65
83
pub ( crate ) static KERBEROS_REALM_NAME_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
@@ -198,28 +216,44 @@ pub fn is_domain(value: &str) -> Result {
198
216
] )
199
217
}
200
218
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)
202
221
/// 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 {
204
223
validate_all ( [
205
224
validate_str_length ( value, RFC_1123_LABEL_MAX_LENGTH ) ,
206
225
validate_str_regex (
207
226
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 ,
210
229
& [ "example-label" , "1-label-1" ] ,
211
230
) ,
212
231
] )
213
232
}
214
233
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 {
217
251
validate_all ( [
218
252
validate_str_length ( value, RFC_1035_LABEL_MAX_LENGTH ) ,
219
253
validate_str_regex (
220
254
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 ,
223
257
& [ "my-name" , "abc-123" ] ,
224
258
) ,
225
259
] )
@@ -261,7 +295,7 @@ pub fn name_is_dns_label(name: &str, prefix: bool) -> Result {
261
295
name = mask_trailing_dash ( name) ;
262
296
}
263
297
264
- is_rfc_1035_label ( & name)
298
+ is_lowercase_rfc_1035_label ( & name)
265
299
}
266
300
267
301
/// Validates a namespace name.
@@ -277,28 +311,14 @@ mod tests {
277
311
278
312
use super :: * ;
279
313
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
-
300
314
#[ rstest]
301
315
#[ case( "" ) ]
316
+ #[ case( "A" ) ]
317
+ #[ case( "aBc" ) ]
318
+ #[ case( "ABC" ) ]
319
+ #[ case( "A1" ) ]
320
+ #[ case( "A-1" ) ]
321
+ #[ case( "1-A" ) ]
302
322
#[ case( "-" ) ]
303
323
#[ case( "a-" ) ]
304
324
#[ case( "-a" ) ]
@@ -325,6 +345,24 @@ mod tests {
325
345
#[ case( "1 " ) ]
326
346
#[ case( " 1" ) ]
327
347
#[ 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" ) ]
328
366
#[ case( "a@b" ) ]
329
367
#[ case( "a,b" ) ]
330
368
#[ case( "a_b" ) ]
@@ -335,77 +373,53 @@ mod tests {
335
373
#[ case( "a$b" ) ]
336
374
#[ case( & "a" . repeat( 254 ) ) ]
337
375
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( ) ) ;
339
377
}
340
378
341
379
#[ rstest]
342
380
#[ case( "a" ) ]
343
- #[ case( "A" ) ]
344
381
#[ case( "ab" ) ]
345
382
#[ case( "abc" ) ]
346
- #[ case( "aBc" ) ]
347
- #[ case( "ABC" ) ]
348
383
#[ case( "a1" ) ]
349
- #[ case( "A1" ) ]
350
384
#[ case( "a-1" ) ]
351
- #[ case( "A-1" ) ]
352
385
#[ case( "a--1--2--b" ) ]
353
386
#[ case( "0" ) ]
354
387
#[ case( "01" ) ]
355
388
#[ case( "012" ) ]
356
389
#[ case( "1a" ) ]
357
390
#[ case( "1-a" ) ]
358
- #[ case( "1-A" ) ]
359
391
#[ case( "1--a--b--2" ) ]
360
392
#[ case( "a.a" ) ]
361
- #[ case( "A.a" ) ]
362
393
#[ case( "ab.a" ) ]
363
- #[ case( "aB.a" ) ]
364
- #[ case( "ab.A" ) ]
365
394
#[ case( "abc.a" ) ]
366
395
#[ case( "a1.a" ) ]
367
- #[ case( "A1.a" ) ]
368
- #[ case( "a1.A" ) ]
369
396
#[ case( "a-1.a" ) ]
370
397
#[ case( "a--1--2--b.a" ) ]
371
398
#[ case( "a.1" ) ]
372
- #[ case( "A.1" ) ]
373
399
#[ case( "ab.1" ) ]
374
- #[ case( "aB.1" ) ]
375
400
#[ case( "abc.1" ) ]
376
401
#[ case( "a1.1" ) ]
377
- #[ case( "A1.1" ) ]
378
402
#[ case( "a-1.1" ) ]
379
403
#[ case( "a--1--2--b.1" ) ]
380
404
#[ case( "0.a" ) ]
381
- #[ case( "0.A" ) ]
382
405
#[ case( "01.a" ) ]
383
- #[ case( "01.A" ) ]
384
406
#[ case( "012.a" ) ]
385
- #[ case( "012.A" ) ]
386
407
#[ case( "1a.a" ) ]
387
- #[ case( "1A.a" ) ]
388
- #[ case( "1a.A" ) ]
389
408
#[ case( "1-a.a" ) ]
390
409
#[ case( "1--a--b--2" ) ]
391
410
#[ case( "0.1" ) ]
392
411
#[ case( "01.1" ) ]
393
412
#[ case( "012.1" ) ]
394
413
#[ case( "1a.1" ) ]
395
- #[ case( "1A.1" ) ]
396
414
#[ case( "1-a.1" ) ]
397
415
#[ case( "1--a--b--2.1" ) ]
398
416
#[ case( "a.b.c.d.e" ) ]
399
- #[ case( "a.B.c.d.e" ) ]
400
- #[ case( "A.B.C.D.E" ) ]
401
417
#[ case( "aa.bb.cc.dd.ee" ) ]
402
- #[ case( "aa.bB.cc.dd.ee" ) ]
403
- #[ case( "AA.BB.CC.DD.EE" ) ]
404
418
#[ case( "1.2.3.4.5" ) ]
405
419
#[ case( "11.22.33.44.55" ) ]
406
420
#[ case( & "a" . repeat( 253 ) ) ]
407
421
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( ) ) ;
409
423
// Every valid RFC1123 is also a valid domain
410
424
assert ! ( is_domain( value) . is_ok( ) ) ;
411
425
}
@@ -469,7 +483,7 @@ mod tests {
469
483
#[ case( "1 2" ) ]
470
484
#[ case( & "a" . repeat( 64 ) ) ]
471
485
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( ) ) ;
473
487
}
474
488
475
489
#[ rstest]
@@ -481,6 +495,6 @@ mod tests {
481
495
#[ case( "a--1--2--b" ) ]
482
496
#[ case( & "a" . repeat( 63 ) ) ]
483
497
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( ) ) ;
485
499
}
486
500
}
0 commit comments