pub(crate) fn xml_escape(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
pub(crate) fn percent_decode(value: &str) -> String {
let bytes = value.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut index = 0;
while index < bytes.len() {
if bytes[index] == b'%' && index + 2 < bytes.len() {
if let Ok(byte) = u8::from_str_radix(&value[index + 1..index + 3], 16) {
out.push(byte);
index += 3;
continue;
}
}
out.push(bytes[index]);
index += 1;
}
String::from_utf8_lossy(&out).into_owned()
}
pub(crate) fn percent_encode(value: &str) -> String {
value
.bytes()
.map(|byte| {
if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'/') {
(byte as char).to_string()
} else {
format!("%{byte:02X}")
}
})
.collect()
}
const BASE64_TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub(crate) fn base64_encode(bytes: &[u8]) -> String {
let mut out = String::new();
for chunk in bytes.chunks(3) {
let b0 = chunk[0];
let b1 = *chunk.get(1).unwrap_or(&0);
let b2 = *chunk.get(2).unwrap_or(&0);
let n = ((b0 as u32) << 16) | ((b1 as u32) << 8) | b2 as u32;
out.push(BASE64_TABLE[((n >> 18) & 0x3f) as usize] as char);
out.push(BASE64_TABLE[((n >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(BASE64_TABLE[((n >> 6) & 0x3f) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(BASE64_TABLE[(n & 0x3f) as usize] as char);
} else {
out.push('=');
}
}
out
}
pub(crate) fn base64_decode(value: &str) -> Option<Vec<u8>> {
let bytes = value.trim().as_bytes();
if bytes.is_empty() || bytes.len() % 4 != 0 {
return None;
}
let mut out = Vec::new();
for chunk in bytes.chunks(4) {
let mut n = 0u32;
let mut padding = 0usize;
for byte in chunk {
n <<= 6;
match *byte {
b'A'..=b'Z' => n |= (*byte - b'A') as u32,
b'a'..=b'z' => n |= (*byte - b'a' + 26) as u32,
b'0'..=b'9' => n |= (*byte - b'0' + 52) as u32,
b'+' => n |= 62,
b'/' => n |= 63,
b'=' => padding += 1,
_ => return None,
}
}
if padding > 2 {
return None;
}
out.push(((n >> 16) & 0xff) as u8);
if padding < 2 {
out.push(((n >> 8) & 0xff) as u8);
}
if padding < 1 {
out.push((n & 0xff) as u8);
}
}
Some(out)
}
pub(crate) fn hex_to_bytes(value: &str) -> Option<Vec<u8>> {
if value.len() % 2 != 0 {
return None;
}
let mut out = Vec::new();
for index in (0..value.len()).step_by(2) {
out.push(u8::from_str_radix(&value[index..index + 2], 16).ok()?);
}
Some(out)
}