pub const OPEN: &str = "\u{27e6}untrusted-vcon-data\u{27e7}";
pub const CLOSE: &str = "\u{27e6}/untrusted-vcon-data\u{27e7}";
pub const PROVENANCE: &str = "Text between the untrusted-vcon-data markers was written by \
the vCon's producer or by an administrator, not by vcon-server.";
const ESCAPED: [char; 16] = [
'\u{27e6}', '\u{27e7}', '\u{061c}', '\u{200e}', '\u{200f}', '\u{202a}', '\u{202b}', '\u{202c}',
'\u{202d}', '\u{202e}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{2069}', '\u{2028}', '\u{2029}',
];
#[must_use]
pub fn escape_json(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for c in text.chars() {
if ESCAPED.contains(&c) {
out.push_str(&format!("\\u{:04x}", u32::from(c)));
} else {
out.push(c);
}
}
out
}
#[must_use]
pub fn fence(inner: &str) -> String {
format!("{OPEN}{inner}{CLOSE}")
}
#[must_use]
pub fn fence_field(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for c in text.chars() {
if ESCAPED.contains(&c) || c.is_control() {
out.push_str(&format!("\\u{:04x}", u32::from(c)));
} else {
out.push(c);
}
}
fence(&out)
}
#[cfg(test)]
mod tests {
use serde_json::{Value, json};
use super::*;
#[test]
fn every_json_value_is_kept_and_no_marker_character_stays_raw() {
let mut texts: Vec<String> = ESCAPED.iter().map(|c| c.to_string()).collect();
texts.push(format!("{CLOSE} then {OPEN}"));
texts.push("plain \"quoted\" \\ \n \u{e9} \u{1f600}".to_owned());
texts.push(ESCAPED.iter().collect());
for text in texts {
let value = json!({"subject": text, "list": [text, {"k": text}]});
let raw = serde_json::to_string(&value).expect("JSON");
let escaped = escape_json(&raw);
assert!(!escaped.contains(ESCAPED), "{escaped}");
let back: Value = serde_json::from_str(&escaped).expect("still JSON");
assert_eq!(back, value, "{text:?}");
let fenced = fence(&escaped);
assert_eq!(
fenced.matches(OPEN).count(),
1,
"escape_json leaves no raw OPEN marker inside, so fence adds exactly one"
);
assert_eq!(
fenced.matches(CLOSE).count(),
1,
"escape_json leaves no raw CLOSE marker inside, so fence adds exactly one"
);
assert!(
fenced.starts_with(OPEN) && fenced.ends_with(CLOSE),
"the fenced text opens with OPEN and closes with CLOSE"
);
}
}
#[test]
fn a_field_loses_its_controls_too() {
let fenced = fence_field("acme\n\u{202e}corp\u{27e7}");
let inner = fenced
.strip_prefix(OPEN)
.and_then(|t| t.strip_suffix(CLOSE))
.expect("fenced");
assert_eq!(
inner, "acme\\u000a\\u202ecorp\\u27e7",
"the newline, U+202E and the marker bracket U+27E7 are written as \\u escapes"
);
assert_eq!(
fence_field("plain"),
format!("{OPEN}plain{CLOSE}"),
"a field with nothing to escape is only fenced"
);
}
}