Skip to content

Commit 8d34df9

Browse files
authored
Merge pull request #35 from buffrr/space-rules
Space naming rules updates
2 parents 5198a46 + 1b58b8f commit 8d34df9

File tree

1 file changed

+99
-18
lines changed

1 file changed

+99
-18
lines changed

protocol/src/slabel.rs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use serde::{de::Error as ErrorUtil, Deserialize, Deserializer, Serialize, Serial
1212
use crate::errors::Error;
1313

1414
pub const MAX_LABEL_LEN: usize = 62;
15+
pub const PUNYCODE_PREFIX: &[u8] = b"xn--";
1516

1617
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1718
pub struct SLabel([u8; MAX_LABEL_LEN + 1]);
@@ -153,23 +154,36 @@ impl<'a> TryFrom<&'a [u8]> for SLabelRef<'a> {
153154
if value.is_empty() {
154155
return Err(Error::Name(NameErrorKind::Empty));
155156
}
156-
let label_len = value[0] as usize;
157-
if label_len == 0 {
157+
let len = value[0] as usize;
158+
if len == 0 {
158159
return Err(Error::Name(NameErrorKind::ZeroLength));
159160
}
160-
if label_len > MAX_LABEL_LEN {
161+
if len > MAX_LABEL_LEN {
161162
return Err(Error::Name(NameErrorKind::TooLong));
162163
}
163-
if label_len + 1 > value.len() {
164+
if len + 1 > value.len() {
164165
return Err(Error::Name(NameErrorKind::EOF));
165166
}
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'-' {
171174
return Err(Error::Name(NameErrorKind::InvalidCharacter));
172175
}
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+
}
173187
Ok(SLabelRef(label))
174188
}
175189
}
@@ -196,16 +210,11 @@ impl TryFrom<&str> for SLabel {
196210
if label.len() > MAX_LABEL_LEN {
197211
return Err(Error::Name(NameErrorKind::TooLong));
198212
}
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-
}
205213
let mut label_bytes = [0; MAX_LABEL_LEN + 1];
206214
label_bytes[0] = label.len() as u8;
207215
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())
209218
}
210219
}
211220

@@ -288,13 +297,50 @@ mod tests {
288297
"Should fail if label contains invalid characters"
289298
);
290299
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 :("
293338
);
294339
assert!(
295340
SLabel::try_from(b"\x07exam").is_err(),
296341
"Should fail if buffer is too short"
297342
);
343+
298344
assert_eq!(
299345
SLabel::try_from(b"\x02exam").unwrap().to_string(),
300346
"@ex",
@@ -305,6 +351,12 @@ mod tests {
305351
b"\x02ex",
306352
"Should work"
307353
);
354+
355+
assert_eq!(
356+
SLabel::try_from(b"\x14xn--hello-world-1234-five-six-seven").unwrap().as_ref(),
357+
b"\x14xn--hello-world-1234",
358+
"Should work"
359+
);
308360
}
309361

310362
#[test]
@@ -374,4 +426,33 @@ mod tests {
374426
);
375427
}
376428
}
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\x00test").is_err());
442+
assert!(SLabel::try_from("@test\ntest").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+
}
377458
}

0 commit comments

Comments
 (0)