1use crate::_alloc_prelude::*;
2use alloc::borrow::Cow;
3use core::fmt::Write as _;
4
5#[must_use]
7pub fn encode_ref_name(name: &str) -> Cow<'_, str> {
8 fn needs_encoding(byte: u8) -> bool {
9 match byte {
10 b'~' | b'/' => true,
13 b'!' | b'$' | b'&'..=b';' | b'=' | b'?'..=b'Z' | b'_' | b'a'..=b'z' => false,
16 _ => true,
18 }
19 }
20
21 if name.bytes().any(needs_encoding) {
22 let mut buf = String::new();
23
24 for byte in name.bytes() {
25 if byte == b'~' {
26 buf.push_str("~0");
27 } else if byte == b'/' {
28 buf.push_str("~1");
29 } else if needs_encoding(byte) {
30 write!(buf, "%{byte:2X}").unwrap();
31 } else {
32 buf.push(byte as char);
33 }
34 }
35
36 Cow::Owned(buf)
37 } else {
38 Cow::Borrowed(name)
39 }
40}
41
42#[must_use]
45pub fn percent_decode(s: &str) -> Option<Cow<'_, str>> {
46 if s.contains('%') {
47 let mut buf = Vec::<u8>::new();
48
49 let mut segments = s.split('%');
50 buf.extend(segments.next().unwrap_or_default().as_bytes());
51
52 for segment in segments {
53 if let Some(decoded_byte) = segment
54 .get(0..2)
55 .and_then(|p| u8::from_str_radix(p, 16).ok())
56 {
57 buf.push(decoded_byte);
58 buf.extend(&segment.as_bytes()[2..]);
59 } else {
60 buf.push(b'%');
61 buf.extend(segment.as_bytes());
62 }
63 }
64
65 String::from_utf8(buf).ok().map(Cow::Owned)
66 } else {
67 Some(Cow::Borrowed(s))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_encode_ref_name() {
77 assert_eq!(encode_ref_name("Simple!"), "Simple!");
78 assert_eq!(
79 encode_ref_name("Needs %-encoding 🚀"),
80 "Needs%20%25-encoding%20%F0%9F%9A%80"
81 );
82 assert_eq!(
83 encode_ref_name("aA0-._!$&'()*+,;=:@?"),
84 "aA0-._!$&'()*+,;=:@?",
85 );
86 assert_eq!(encode_ref_name("\"£%^\\~/"), "%22%C2%A3%25%5E%5C~0~1",);
87 }
88
89 #[test]
90 fn test_percent_decode() {
91 assert_eq!(percent_decode("Simple!"), Some("Simple!".into()));
92 assert_eq!(
93 percent_decode("Needs %-encoding 🚀"),
94 Some("Needs %-encoding 🚀".into())
95 );
96 assert_eq!(
97 percent_decode("Needs%20%25-encoding%20%F0%9F%9A%80"),
98 Some("Needs %-encoding 🚀".into())
99 );
100 assert_eq!(
101 percent_decode("aA0-._!$&'()*+,;=:@?"),
102 Some("aA0-._!$&'()*+,;=:@?".into())
103 );
104 assert_eq!(percent_decode("\"£%^\\~/"), Some("\"£%^\\~/".into()));
105 assert_eq!(
106 percent_decode("%22%C2%A3%25%5E%5C~0~1"),
107 Some("\"£%^\\~0~1".into())
108 );
109 assert_eq!(percent_decode("%%%2020%%%"), Some("%% 20%%%".into()));
110 assert_eq!(percent_decode("%f0%9F%9a%80"), Some("🚀".into()));
111 assert_eq!(percent_decode("%F0%9F%9A"), None);
112 }
113}