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