#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::borrow::Cow;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use alloc::string::String;
pub fn unescape_ldap_value_inplace(bytes: &mut [u8]) -> usize {
if bytes.is_empty() {
return 0;
}
let mut read_pos = 0;
let mut write_pos = 0;
while read_pos < bytes.len() {
if bytes[read_pos] == b'\\' && read_pos + 1 < bytes.len() {
let next_byte = bytes[read_pos + 1];
if next_byte.is_ascii_hexdigit() && read_pos + 2 < bytes.len() {
let hex_high = next_byte;
let hex_low = bytes[read_pos + 2];
if hex_low.is_ascii_hexdigit() {
let decoded_byte = hex_to_byte(hex_high, hex_low);
bytes[write_pos] = decoded_byte;
write_pos += 1;
read_pos += 3; continue;
}
} else if is_backslash_escaped_char(next_byte) {
bytes[write_pos] = next_byte;
write_pos += 1;
read_pos += 2; continue;
}
}
bytes[write_pos] = bytes[read_pos];
write_pos += 1;
read_pos += 1;
}
write_pos
}
fn hex_to_byte(high: u8, low: u8) -> u8 {
let high_val = match high {
b'0'..=b'9' => high - b'0',
b'A'..=b'F' => high - b'A' + 10,
b'a'..=b'f' => high - b'a' + 10,
_ => 0,
};
let low_val = match low {
b'0'..=b'9' => low - b'0',
b'A'..=b'F' => low - b'A' + 10,
b'a'..=b'f' => low - b'a' + 10,
_ => 0,
};
(high_val << 4) | low_val
}
#[inline]
fn is_backslash_escaped_char(byte: u8) -> bool {
matches!(byte, b' ' | b'"' | b'#' | b'+' | b',' | b';' | b'<' | b'=' | b'>' | b'\\')
}
pub fn unescape_ldap_value_string(s: &mut str) -> Result<&str, core::str::Utf8Error> {
let mut bytes = unsafe {
s.as_bytes_mut()
};
let len = unescape_ldap_value_inplace(&mut bytes);
core::str::from_utf8(&bytes[..len])
}
#[cfg(feature = "alloc")]
pub fn unescape_ldap_value_cow(bytes: &[u8]) -> Cow<[u8]> {
let needs_unescaping = bytes.iter().any(|b| *b == b'\\');
if !needs_unescaping {
return Cow::Borrowed(bytes);
}
let mut result = Vec::with_capacity(bytes.len());
let mut read_pos = 0;
while read_pos < bytes.len() {
if bytes[read_pos] == b'\\' && read_pos + 1 < bytes.len() {
let next_byte = bytes[read_pos + 1];
if next_byte.is_ascii_hexdigit() && read_pos + 2 < bytes.len() {
let hex_high = next_byte;
let hex_low = bytes[read_pos + 2];
if hex_low.is_ascii_hexdigit() {
let decoded_byte = hex_to_byte(hex_high, hex_low);
result.push(decoded_byte);
read_pos += 3;
continue;
}
} else if is_backslash_escaped_char(next_byte) {
result.push(next_byte);
read_pos += 2;
continue;
}
}
result.push(bytes[read_pos]);
read_pos += 1;
}
Cow::Owned(result)
}
#[cfg(feature = "alloc")]
pub fn unescape_ldap_value_string_cow(s: &str) -> Result<Cow<str>, core::str::Utf8Error> {
let bytes_result = unescape_ldap_value_cow(s.as_bytes());
match bytes_result {
Cow::Borrowed(bytes) => {
debug_assert_eq!(bytes, s.as_bytes());
Ok(Cow::Borrowed(s))
}
Cow::Owned(bytes) => {
let unescaped_str = core::str::from_utf8(&bytes)?;
Ok(Cow::Owned(String::from(unescaped_str)))
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use super::*;
#[test]
fn test_unescape_ldap_value_inplace_empty() {
let mut bytes = [];
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 0);
}
#[test]
fn test_unescape_ldap_value_inplace_no_escapes() {
let mut bytes = b"Hello World".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 11);
assert_eq!(&bytes[..result], b"Hello World");
}
#[test]
fn test_unescape_ldap_value_inplace_hex_escapes() {
let mut bytes = b"\\00\\01\\02\\03".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 4);
assert_eq!(&bytes[..result], &[0x00, 0x01, 0x02, 0x03]);
}
#[test]
fn test_unescape_ldap_value_inplace_mixed_escapes() {
let mut bytes = b"\\00Hello\\, World\\\"!\\01".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 16);
assert_eq!(&bytes[..result], &[0x00, b'H', b'e', b'l', b'l', b'o', b',', b' ', b'W', b'o', b'r', b'l', b'd', b'"', b'!', 0x01]);
}
#[test]
fn test_unescape_ldap_value_inplace_all_backslash_chars() {
let mut bytes = b"\\ \\\"\\#\\+\\,\\;\\<\\=\\>\\\\".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 10);
assert_eq!(&bytes[..result], b" \"#+,;<=>\\");
}
#[test]
fn test_unescape_ldap_value_inplace_hex_uppercase() {
let mut bytes = b"\\0A\\1B\\2C\\3D\\4E\\5F".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 6);
assert_eq!(&bytes[..result], &[0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
}
#[test]
fn test_unescape_ldap_value_inplace_hex_lowercase() {
let mut bytes = b"\\0a\\1b\\2c\\3d\\4e\\5f".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 6);
assert_eq!(&bytes[..result], &[0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
}
#[test]
fn test_unescape_ldap_value_inplace_hex_mixed_case() {
let mut bytes = b"\\0A\\1b\\2C\\3d".to_vec();
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 4);
assert_eq!(&bytes[..result], &[0x0A, 0x1B, 0x2C, 0x3D]);
}
#[test]
fn test_unescape_ldap_value_inplace_incomplete_hex() {
let mut bytes = Vec::from(b"\\0");
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 2);
assert_eq!(&bytes[..result], b"\\0");
}
#[test]
fn test_unescape_ldap_value_inplace_invalid_hex() {
let mut bytes = Vec::from(b"\\0G");
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 3);
assert_eq!(&bytes[..result], b"\\0G");
}
#[test]
fn test_unescape_ldap_value_inplace_lone_backslash() {
let mut bytes = Vec::from(b"Hello\\World");
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 11);
assert_eq!(&bytes[..result], b"Hello\\World");
}
#[test]
fn test_unescape_ldap_value_inplace_null_character() {
let mut bytes = Vec::from(b"Hello\\00World");
let result = unescape_ldap_value_inplace(&mut bytes);
assert_eq!(result, 11);
assert_eq!(&bytes[..result], &[b'H', b'e', b'l', b'l', b'o', 0x00, b'W', b'o', b'r', b'l', b'd']);
}
#[test]
fn test_unescape_ldap_value_string() {
let mut s = String::from("James \"Jim\" Smith, III");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "James \"Jim\" Smith, III");
}
#[test]
fn test_unescape_ldap_value_string_with_escapes() {
let mut s = String::from("James \\\"Jim\\\" Smith\\, III");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "James \"Jim\" Smith, III");
}
#[test]
fn test_unescape_ldap_value_string_hex_escapes() {
let mut s = String::from("\\00\\01\\02\\03");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result.as_bytes(), &[0x00, 0x01, 0x02, 0x03]);
}
#[test]
fn test_unescape_ldap_value_string_complex() {
let mut s = String::from("CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "CN=James \"Jim\" Smith, III,DC=example,DC=net");
}
#[test]
fn test_unescape_ldap_value_string_utf8() {
let mut s = String::from("\\00\\01Hello\\, \\\"World\\\"\\02");
let result = unescape_ldap_value_string(&mut s).unwrap();
let expected_bytes = &[0x00, 0x01, b'H', b'e', b'l', b'l', b'o', b',', b' ', b'"', b'W', b'o', b'r', b'l', b'd', b'"', 0x02];
assert_eq!(result.as_bytes(), expected_bytes);
}
#[test]
fn test_unescape_ldap_value_string_edge_cases() {
let mut s = String::from("");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "");
let mut s = String::from("\\");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "\\");
let mut s = String::from("Hello\\");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "Hello\\");
}
#[test]
fn test_unescape_ldap_value_string_all_escaped_chars() {
let mut s = String::from("\\ \\\"\\#\\+\\,\\;\\<\\=\\>\\\\");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, " \"#+,;<=>\\");
}
#[test]
fn test_unescape_ldap_value_string_hex_edge_cases() {
let mut s = String::from("\\0");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "\\0");
let mut s = String::from("\\0G");
let result = unescape_ldap_value_string(&mut s).unwrap();
assert_eq!(result, "\\0G");
let mut s = String::from("\\00\\0G\\01");
let result = unescape_ldap_value_string(&mut s).unwrap();
let expected_bytes = &[0x00, b'\\', b'0', b'G', 0x01];
assert_eq!(result.as_bytes(), expected_bytes);
}
}