From 2aa45df605de6e4026c3e7848996bdbcf7c308d3 Mon Sep 17 00:00:00 2001 From: Ryan Kuhl Date: Wed, 14 Oct 2026 17:43:26 -0400 Subject: [PATCH] fix: leave out a document whose converted text carries a NUL byte The store refuses source text that carries a NUL, and every other source obeys that contract before it is sent: a text file with a NUL is left out as `unsupported_binary` (local.rs), and a connector document with one is refused (connectors.rs). Converted document text was the one path that did not check: `bounded()` checked only length and emptiness, so a converter that emits a NUL for a glyph with no Unicode mapping (pdf-extract does, on a math font) had its upload refused, and the failed step aborted the whole sync with "Invalid source text", naming no file. `bounded()` now refuses a NUL the way it refuses text past the limit: the document is listed in the sync result ("Document not converted: Extracted text contains a NUL character") and the rest of the folder syncs. No single file fails a sync. Signed-off-by: Ryan Kuhl --- crates/graphicle/src/documents.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/graphicle/src/documents.rs b/crates/graphicle/src/documents.rs index db4ace2..a711ca2 100644 --- a/crates/graphicle/src/documents.rs +++ b/crates/graphicle/src/documents.rs @@ -96,6 +96,10 @@ fn bounded(text: String) -> Result { "No readable text was extracted; OCR may be required", )); } + // A converter emits a NUL for a glyph with no Unicode mapping; the store refuses text with one. + if text.contains('\0') { + return Err(Error::bad("Extracted text contains a NUL character")); + } Ok(text) } pub fn xml_text(bytes: &[u8]) -> Result { @@ -712,3 +716,14 @@ impl Store { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extracted_text_with_a_nul_is_refused() { + let error = bounded("before\u{0}after".to_string()).unwrap_err(); + assert!(error.message.contains("NUL"), "{error:?}"); + } +}