use super::pipeline::push_decoded_text_chunk_spliced;
use super::util::take_hex_digits;
use super::Decoder;
use keyhog_core::Chunk;
pub(super) struct JsonDecoder;
impl Decoder for JsonDecoder {
fn name(&self) -> &'static str {
"json"
}
fn decode_chunk(&self, chunk: &Chunk) -> Vec<Chunk> {
let mut decoded_chunks = Vec::new();
for json_string in extract_escaped_json_strings(&chunk.data) {
if let Ok(unescaped) = json_unescape(json_string) {
push_decoded_text_chunk_spliced(
&mut decoded_chunks,
chunk,
json_string,
unescaped,
self.name(),
);
}
}
decoded_chunks
}
}
fn extract_escaped_json_strings(text: &str) -> Vec<&str> {
let mut strings = Vec::new();
let bytes = text.as_bytes();
let mut index = 0;
while index < bytes.len() {
if let Some(quote_idx) = memchr::memchr(b'"', &bytes[index..]) {
index += quote_idx;
} else {
break;
}
index += 1;
let content_start = index;
let mut saw_escape = false;
let mut closed = false;
while index < bytes.len() {
match bytes[index] {
b'\\' => {
saw_escape = true;
index = index.saturating_add(2);
}
b'"' => {
let content_end = index;
index += 1;
closed = true;
if saw_escape && content_end.saturating_sub(content_start) >= 4 {
strings.push(&text[content_start..content_end]);
}
break;
}
b'\n' | b'\r' => {
break;
}
_ => index += 1,
}
}
if closed {
continue;
}
index += 1;
}
strings
}
fn json_unescape(input: &str) -> Result<String, ()> {
let mut decoded = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(ch) = chars.next() {
if ch != '\\' {
decoded.push(ch);
continue;
}
match chars.next() {
Some('"') => decoded.push('"'),
Some('\\') => decoded.push('\\'),
Some('/') => decoded.push('/'),
Some('b') => decoded.push('\x08'),
Some('f') => decoded.push('\x0C'),
Some('n') => decoded.push('\n'),
Some('r') => decoded.push('\r'),
Some('t') => decoded.push('\t'),
Some('u') => {
let code = take_hex_digits(&mut chars, 4)?;
decoded.push(char::from_u32(code).ok_or(())?);
}
_ => return Err(()),
}
}
Ok(decoded)
}