use crate::serde_json::{JsonEncode, Value};
pub struct SerializedJsonField;
impl SerializedJsonField {
pub fn tainted(s: &str) -> Value {
Value::String(s.to_string())
}
pub fn typed<T: JsonEncode + ?Sized>(value: &T) -> Value {
value.to_json_value()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serde_json::{from_str, Map};
fn round_trip(input: &str) -> String {
let mut obj = Map::new();
obj.insert("field".to_string(), SerializedJsonField::tainted(input));
let envelope = Value::Object(obj).to_string_compact();
let parsed: Value = from_str(&envelope).expect("envelope must be valid JSON");
match parsed {
Value::Object(map) => match map.get("field").cloned() {
Some(Value::String(s)) => s,
other => panic!("field missing or wrong shape: {other:?}"),
},
other => panic!("expected object, got {other:?}"),
}
}
#[test]
fn json_field_tainted_round_trips_quote_smuggling_attempt() {
let payload = r#"val"; "injected": true"#;
assert_eq!(round_trip(payload), payload);
}
#[test]
fn json_field_tainted_round_trips_crlf_in_value() {
let payload = "first line\r\n\"injected_key\": \"x";
assert_eq!(round_trip(payload), payload);
}
#[test]
fn json_field_tainted_escapes_all_control_bytes() {
for byte in 0x00u8..0x20 {
let payload: String = char::from_u32(byte as u32).unwrap().to_string();
let mut obj = Map::new();
obj.insert("k".to_string(), SerializedJsonField::tainted(&payload));
let envelope = Value::Object(obj).to_string_compact();
assert!(
!envelope.as_bytes().contains(&byte) || byte == b'\n' && envelope.contains("\\n"),
"byte 0x{byte:02x} appeared raw in envelope: {envelope:?}"
);
assert_eq!(round_trip(&payload), payload);
}
}
#[test]
fn json_field_tainted_round_trips_existing_escape_sequences() {
let payload = r#"contains \n and \t as literal chars"#;
assert_eq!(round_trip(payload), payload);
}
#[test]
fn json_field_tainted_round_trips_deeply_nested_escapes() {
let payload =
r#"{"outer":"{\"inner\":\"{\\\"deepest\\\":\\\"\\\\\\\"end\\\\\\\"\\\"}\"}"}"#;
assert_eq!(round_trip(payload), payload);
}
#[test]
fn json_field_tainted_round_trips_when_used_as_object_key() {
let key = "key\"with\\quotes\nand-newlines";
let mut obj = Map::new();
obj.insert(key.to_string(), SerializedJsonField::tainted("v"));
let envelope = Value::Object(obj).to_string_compact();
let parsed: Value = from_str(&envelope).expect("envelope must be valid JSON");
match parsed {
Value::Object(map) => assert!(
map.get(key).is_some(),
"key did not round-trip; map keys: {:?}",
map.keys().collect::<Vec<_>>()
),
other => panic!("expected object, got {other:?}"),
}
}
#[test]
fn json_field_tainted_round_trips_unicode_and_emoji() {
let payload = "café — naïve façade — 日本語 — 🦀";
assert_eq!(round_trip(payload), payload);
}
#[test]
fn json_field_tainted_round_trips_multibyte_utf8_corpus() {
let corpus: &[&str] = &[
"café — naïve façade — Œuvre",
"日本語テスト — 中文 — 한국어",
"🦀🚀💩🌍 family: 👨👩👧👦",
"Hello مرحبا שלום — mix",
];
for payload in corpus {
assert_eq!(round_trip(payload), *payload, "payload {payload:?}");
}
}
#[test]
fn json_field_typed_emits_canonical_representation() {
let v = SerializedJsonField::typed(&42_i64);
assert_eq!(v.as_i64(), Some(42));
let v = SerializedJsonField::typed(&true);
assert_eq!(v.as_bool(), Some(true));
let v = SerializedJsonField::typed(&"hello");
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn json_field_tainted_handles_full_malicious_corpus() {
let corpus: &[&str] = &[
r#"{"key": "val"; "injected": true}"#,
"line1\r\nContent-Length: 0\r\n\r\nhost: evil",
"control\x00bytes\x01\x02\x03\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x1fend",
"escapes \\n \\r \\t \\u0041 \\\\ \\\" literal",
r#"deeply{"nested":{"json":{"inside":"of","a":"string"}}}deeply"#,
"trailing-newline\n",
"\"-prefixed",
"back\\slash-suffix\\",
r#"sql parse error: unexpected token "}" near "select * from t where j = '{\"x\":1}'""#,
];
for payload in corpus {
assert_eq!(round_trip(payload), *payload, "corpus payload: {payload:?}");
}
}
}