@@ -12,6 +12,7 @@ use serde::{de::Error as ErrorUtil, Deserialize, Deserializer, Serialize, Serial
12
12
use crate :: errors:: Error ;
13
13
14
14
pub const MAX_LABEL_LEN : usize = 62 ;
15
+ pub const PUNYCODE_PREFIX : & [ u8 ] = b"xn--" ;
15
16
16
17
#[ derive( Clone , Debug , PartialEq , Eq , PartialOrd , Ord ) ]
17
18
pub struct SLabel ( [ u8 ; MAX_LABEL_LEN + 1 ] ) ;
@@ -153,23 +154,36 @@ impl<'a> TryFrom<&'a [u8]> for SLabelRef<'a> {
153
154
if value. is_empty ( ) {
154
155
return Err ( Error :: Name ( NameErrorKind :: Empty ) ) ;
155
156
}
156
- let label_len = value[ 0 ] as usize ;
157
- if label_len == 0 {
157
+ let len = value[ 0 ] as usize ;
158
+ if len == 0 {
158
159
return Err ( Error :: Name ( NameErrorKind :: ZeroLength ) ) ;
159
160
}
160
- if label_len > MAX_LABEL_LEN {
161
+ if len > MAX_LABEL_LEN {
161
162
return Err ( Error :: Name ( NameErrorKind :: TooLong ) ) ;
162
163
}
163
- if label_len + 1 > value. len ( ) {
164
+ if len + 1 > value. len ( ) {
164
165
return Err ( Error :: Name ( NameErrorKind :: EOF ) ) ;
165
166
}
166
- let label = & value[ ..=label_len] ;
167
- if !label[ 1 ..]
168
- . iter ( )
169
- . all ( |& b| b. is_ascii_lowercase ( ) || b. is_ascii_digit ( ) )
170
- {
167
+ let label = & value[ ..=len] ;
168
+ let mut verify_range = & label[ 1 ..] ;
169
+ if verify_range. starts_with ( PUNYCODE_PREFIX ) && len > PUNYCODE_PREFIX . len ( ) {
170
+ verify_range = & verify_range[ PUNYCODE_PREFIX . len ( ) ..]
171
+ }
172
+
173
+ if verify_range[ 0 ] == b'-' || verify_range[ verify_range. len ( ) - 1 ] == b'-' {
171
174
return Err ( Error :: Name ( NameErrorKind :: InvalidCharacter ) ) ;
172
175
}
176
+ let mut prev: u8 = 0 ;
177
+ for c in verify_range {
178
+ match c {
179
+ b'-' if prev == b'-' => return Err ( Error :: Name ( NameErrorKind :: InvalidCharacter ) ) ,
180
+ b'a' ..=b'z' | b'0' ..=b'9' | b'-' => {
181
+ prev = * c;
182
+ continue ;
183
+ }
184
+ _ => return Err ( Error :: Name ( NameErrorKind :: InvalidCharacter ) ) ,
185
+ }
186
+ }
173
187
Ok ( SLabelRef ( label) )
174
188
}
175
189
}
@@ -196,16 +210,11 @@ impl TryFrom<&str> for SLabel {
196
210
if label. len ( ) > MAX_LABEL_LEN {
197
211
return Err ( Error :: Name ( NameErrorKind :: TooLong ) ) ;
198
212
}
199
- if !label
200
- . bytes ( )
201
- . all ( |b| b. is_ascii_lowercase ( ) || b. is_ascii_digit ( ) )
202
- {
203
- return Err ( Error :: Name ( NameErrorKind :: InvalidCharacter ) ) ;
204
- }
205
213
let mut label_bytes = [ 0 ; MAX_LABEL_LEN + 1 ] ;
206
214
label_bytes[ 0 ] = label. len ( ) as u8 ;
207
215
label_bytes[ 1 ..=label. len ( ) ] . copy_from_slice ( label. as_bytes ( ) ) ;
208
- Ok ( SLabel ( label_bytes) )
216
+
217
+ SLabel :: try_from ( label_bytes. as_slice ( ) )
209
218
}
210
219
}
211
220
@@ -288,13 +297,50 @@ mod tests {
288
297
"Should fail if label contains invalid characters"
289
298
) ;
290
299
assert ! (
291
- SLabel :: try_from( "@example-ok" ) . is_err( ) ,
292
- "Should fail if label contains hyphens"
300
+ SLabel :: try_from( "@example-ok" ) . is_ok( ) ,
301
+ "Should work with single hyphens"
302
+ ) ;
303
+ assert ! (
304
+ SLabel :: try_from( "@example-ok-ok2-ok3" ) . is_ok( ) ,
305
+ "Multiple non consecutive hyphens should work"
306
+ ) ;
307
+ assert ! (
308
+ SLabel :: try_from( "@example--ok" ) . is_err( ) ,
309
+ "Should not work with double hyphens"
310
+ ) ;
311
+ assert ! (
312
+ SLabel :: try_from( "@-ok" ) . is_err( ) ,
313
+ "Should not work with hyphens start"
314
+ ) ;
315
+ assert ! (
316
+ SLabel :: try_from( "@ok-" ) . is_err( ) ,
317
+ "Should not work with hyphens at end"
318
+ ) ;
319
+ assert ! (
320
+ SLabel :: try_from( "@xn--" ) . is_err( ) ,
321
+ "Should not work with empty punycode"
322
+ ) ;
323
+ assert ! (
324
+ SLabel :: try_from( "@xn---" ) . is_err( ) ,
325
+ "Should not work with single hyphen punycode"
326
+ ) ;
327
+ assert ! (
328
+ SLabel :: try_from( "@xn--1" ) . is_ok( ) ,
329
+ "Should be okay"
330
+ ) ;
331
+ assert ! (
332
+ SLabel :: try_from( "@0xn--1" ) . is_err( ) ,
333
+ "Should not be okay"
334
+ ) ;
335
+ assert ! (
336
+ SLabel :: try_from( "@xn--123-pretty-valid-space-ok" ) . is_ok( ) ,
337
+ "Should work :("
293
338
) ;
294
339
assert ! (
295
340
SLabel :: try_from( b"\x07 exam" ) . is_err( ) ,
296
341
"Should fail if buffer is too short"
297
342
) ;
343
+
298
344
assert_eq ! (
299
345
SLabel :: try_from( b"\x02 exam" ) . unwrap( ) . to_string( ) ,
300
346
"@ex" ,
@@ -305,6 +351,12 @@ mod tests {
305
351
b"\x02 ex" ,
306
352
"Should work"
307
353
) ;
354
+
355
+ assert_eq ! (
356
+ SLabel :: try_from( b"\x14 xn--hello-world-1234-five-six-seven" ) . unwrap( ) . as_ref( ) ,
357
+ b"\x14 xn--hello-world-1234" ,
358
+ "Should work"
359
+ ) ;
308
360
}
309
361
310
362
#[ test]
@@ -374,4 +426,33 @@ mod tests {
374
426
) ;
375
427
}
376
428
}
429
+
430
+ #[ test]
431
+ fn test_empty_and_null_cases ( ) {
432
+ assert ! ( SLabel :: try_from( "" ) . is_err( ) ) ;
433
+ assert ! ( SLabel :: try_from( "@" ) . is_err( ) ) ;
434
+ assert ! ( SLabel :: try_from( b"" ) . is_err( ) ) ;
435
+ assert ! ( SLabel :: try_from( b"\x00 " ) . is_err( ) ) ;
436
+ }
437
+
438
+ #[ test]
439
+ fn test_unicode_and_special_chars ( ) {
440
+ assert ! ( SLabel :: try_from( "@café" ) . is_err( ) ) ;
441
+ assert ! ( SLabel :: try_from( "@test\x00 test" ) . is_err( ) ) ;
442
+ assert ! ( SLabel :: try_from( "@test\n test" ) . is_err( ) ) ;
443
+ }
444
+
445
+ #[ test]
446
+ fn test_edge_length_cases ( ) {
447
+ assert ! ( SLabel :: try_from( b"\xff " ) . is_err( ) ) ; // Length byte but no content
448
+ assert ! ( SLabel :: try_from( b"\x01 " ) . is_err( ) ) ; // Length byte claims 1 but no content
449
+ }
450
+
451
+ #[ test]
452
+ fn test_punycode_edge_cases ( ) {
453
+ assert ! ( SLabel :: try_from( "@xn" ) . is_ok( ) ) ;
454
+ assert ! ( SLabel :: try_from( "@xn-" ) . is_err( ) ) ;
455
+ assert ! ( SLabel :: try_from( "@xn--" ) . is_err( ) ) ;
456
+ assert ! ( SLabel :: try_from( "@xxn--test" ) . is_err( ) ) ;
457
+ }
377
458
}
0 commit comments