fix: Properly escape JSON string output in CLI binaries
The hand-rolled JSON escaping only handled \, ", and \n but missed tabs, carriage returns, and other control characters (U+0000..U+001F), producing invalid JSON that Python's json.loads would reject. Add a proper json_escape() function covering all required escapes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7a5af1e9c3
commit
23056dc5ba
+23
-1
@@ -5,9 +5,31 @@ use pdf_inspector::{
|
||||
ProcessMode,
|
||||
};
|
||||
use std::env;
|
||||
use std::fmt::Write;
|
||||
use std::process;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Escape a string for embedding in a JSON string value.
|
||||
fn json_escape(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len() + 16);
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'"' => out.push_str("\\\""),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
'\x08' => out.push_str("\\b"),
|
||||
'\x0C' => out.push_str("\\f"),
|
||||
c if c < '\x20' => {
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
@@ -152,7 +174,7 @@ fn run_detect_only(pdf_path: &str, json_output: bool, start: Instant) {
|
||||
result
|
||||
.title
|
||||
.as_ref()
|
||||
.map(|t| format!("\"{}\"", t.replace('"', "\\\"")))
|
||||
.map(|t| format!("\"{}\"", json_escape(t)))
|
||||
.unwrap_or_else(|| "null".to_string()),
|
||||
result.ocr_recommended,
|
||||
ocr_pages.join(","),
|
||||
|
||||
+26
-5
@@ -6,9 +6,34 @@ use pdf_inspector::{
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::process;
|
||||
|
||||
/// Escape a string for embedding in a JSON string value.
|
||||
///
|
||||
/// Handles all characters that the JSON spec requires to be escaped:
|
||||
/// backslash, double-quote, and control characters U+0000..U+001F.
|
||||
fn json_escape(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len() + 16);
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'"' => out.push_str("\\\""),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
'\x08' => out.push_str("\\b"),
|
||||
'\x0C' => out.push_str("\\f"),
|
||||
c if c < '\x20' => {
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Parse a page specification like "1,3,5-10,20" into a HashSet of page numbers.
|
||||
fn parse_page_spec(spec: &str) -> Result<HashSet<u32>, String> {
|
||||
let mut pages = HashSet::new();
|
||||
@@ -185,11 +210,7 @@ fn main() {
|
||||
let md_escaped = result
|
||||
.markdown
|
||||
.as_ref()
|
||||
.map(|m| {
|
||||
m.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('\n', "\\n")
|
||||
})
|
||||
.map(|m| json_escape(m))
|
||||
.unwrap_or_default();
|
||||
|
||||
let ocr_pages: Vec<String> = result
|
||||
|
||||
Reference in New Issue
Block a user