#[cfg(not(feature = "std"))]
use alloc::format;
use crate::io::traits::BencodeWrite;
#[allow(dead_code)]
pub(crate) fn escape_string(value: &str, destination: &mut (impl BencodeWrite + ?Sized)) {
for &byte in value.as_bytes() {
if byte == b'"' || byte == b'\\' {
destination.write_byte(b'\\');
destination.write_byte(byte);
} else if byte.is_ascii_graphic() || byte == b' ' {
destination.write_byte(byte);
} else {
let escaped = format!("\\u{:04x}", byte);
destination.write_bytes(escaped.as_bytes());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::destinations::buffer::Buffer;
#[test]
fn test_escape_regular_string() {
let mut destination = Buffer::new();
escape_string("hello world", &mut destination);
assert_eq!(destination.to_string(), "hello world");
}
#[test]
fn test_escape_special_characters() {
let mut destination = Buffer::new();
escape_string("hello\"world\\test", &mut destination);
assert_eq!(destination.to_string(), "hello\\\"world\\\\test");
}
#[test]
fn test_escape_unprintable_characters() {
let mut destination = Buffer::new();
escape_string("hello\nworld\t", &mut destination);
assert_eq!(destination.to_string(), "hello\\u000aworld\\u0009");
}
#[test]
fn test_escape_empty_string() {
let mut destination = Buffer::new();
escape_string("", &mut destination);
assert_eq!(destination.to_string(), "");
}
#[test]
fn test_escape_null_byte() {
let mut destination = Buffer::new();
escape_string("hello\x00world", &mut destination);
assert_eq!(destination.to_string(), "hello\\u0000world");
}
#[test]
fn test_escape_carriage_return() {
let mut destination = Buffer::new();
escape_string("line1\rline2", &mut destination);
assert_eq!(destination.to_string(), "line1\\u000dline2");
}
#[test]
fn test_escape_backspace() {
let mut destination = Buffer::new();
escape_string("hello\x08world", &mut destination);
assert_eq!(destination.to_string(), "hello\\u0008world");
}
#[test]
fn test_escape_form_feed() {
let mut destination = Buffer::new();
escape_string("page1\x0Cpage2", &mut destination);
assert_eq!(destination.to_string(), "page1\\u000cpage2");
}
#[test]
fn test_escape_vertical_tab() {
let mut destination = Buffer::new();
escape_string("line1\x0Bline2", &mut destination);
assert_eq!(destination.to_string(), "line1\\u000bline2");
}
#[test]
fn test_escape_delete_char() {
let mut destination = Buffer::new();
escape_string("text\x7Fmore", &mut destination);
assert_eq!(destination.to_string(), "text\\u007fmore");
}
#[test]
fn test_escape_mixed_special_chars() {
let mut destination = Buffer::new();
escape_string("\"test\"\n\t\r\x00", &mut destination);
assert_eq!(
destination.to_string(),
"\\\"test\\\"\\u000a\\u0009\\u000d\\u0000"
);
}
#[test]
fn test_escape_all_printable_ascii() {
let mut destination = Buffer::new();
escape_string("abc123!@#$%^&*()", &mut destination);
assert_eq!(destination.to_string(), "abc123!@#$%^&*()");
}
#[test]
fn test_escape_space_is_preserved() {
let mut destination = Buffer::new();
escape_string("a b c", &mut destination);
assert_eq!(destination.to_string(), "a b c");
}
#[test]
fn test_escape_only_quote() {
let mut destination = Buffer::new();
escape_string("\"", &mut destination);
assert_eq!(destination.to_string(), "\\\"");
}
#[test]
fn test_escape_only_backslash() {
let mut destination = Buffer::new();
escape_string("\\", &mut destination);
assert_eq!(destination.to_string(), "\\\\");
}
#[test]
fn test_escape_consecutive_quotes() {
let mut destination = Buffer::new();
escape_string("\"\"\"", &mut destination);
assert_eq!(destination.to_string(), "\\\"\\\"\\\"");
}
#[test]
fn test_escape_consecutive_backslashes() {
let mut destination = Buffer::new();
escape_string("\\\\", &mut destination);
assert_eq!(destination.to_string(), "\\\\\\\\");
}
#[test]
fn test_escape_quote_and_backslash_adjacent() {
let mut destination = Buffer::new();
escape_string("\\\"", &mut destination);
assert_eq!(destination.to_string(), "\\\\\\\"");
}
#[test]
fn test_escape_high_utf8_bytes() {
let mut destination = Buffer::new();
escape_string("é", &mut destination);
assert_eq!(destination.to_string(), "\\u00c3\\u00a9");
}
#[test]
fn test_escape_latin_y_diaeresis() {
let mut destination = Buffer::new();
escape_string("ÿ", &mut destination);
assert_eq!(destination.to_string(), "\\u00c3\\u00bf");
}
#[test]
fn test_escape_byte_0x1f() {
let mut destination = Buffer::new();
escape_string("\x1f", &mut destination);
assert_eq!(destination.to_string(), "\\u001f");
}
#[test]
fn test_escape_leading_and_trailing_special_chars() {
let mut destination = Buffer::new();
escape_string("\ntext\n", &mut destination);
assert_eq!(destination.to_string(), "\\u000atext\\u000a");
}
#[test]
fn test_escape_only_unprintable_sequence() {
let mut destination = Buffer::new();
escape_string("\x01\x02\x03", &mut destination);
assert_eq!(destination.to_string(), "\\u0001\\u0002\\u0003");
}
#[test]
fn test_escape_length_increases_for_escaped_chars() {
let mut dest_plain = Buffer::new();
let mut dest_quoted = Buffer::new();
escape_string("a", &mut dest_plain);
escape_string("\"", &mut dest_quoted);
assert_eq!(dest_plain.to_string().len(), 1);
assert_eq!(dest_quoted.to_string().len(), 2);
}
#[test]
fn test_escape_unprintable_length_is_six() {
let mut destination = Buffer::new();
escape_string("\n", &mut destination);
assert_eq!(destination.to_string().len(), 6);
}
#[test]
fn test_escape_long_string_no_special_chars() {
let input: String = "a".repeat(1000);
let mut destination = Buffer::new();
escape_string(&input, &mut destination);
assert_eq!(destination.to_string(), input);
}
#[test]
fn test_escape_is_idempotent_for_plain_text() {
let plain = "hello world";
let mut dest1 = Buffer::new();
escape_string(plain, &mut dest1);
let first = dest1.to_string();
assert_eq!(first, plain);
}
#[test]
fn test_escape_all_digits_and_letters_preserved() {
let input = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut destination = Buffer::new();
escape_string(input, &mut destination);
assert_eq!(destination.to_string(), input);
}
}