diff --git a/Cargo.toml b/Cargo.toml index a230b9a..2bcfa94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,26 @@ serde_json = "1" [[test]] name = "real_world_samples" required-features = ["alloc"] + +[lints.rust] +unsafe_code = "forbid" + +[lints.clippy] +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +correctness = "deny" +suspicious = "deny" +unwrap_used = "deny" +expect_used = "deny" +# The public API is `fn f(input: I)` — generic-by-value is the +# design seam that lets a caller pass &str, &[u8] or String unchanged. Taking +# `&I` instead would break every caller and defeat the trait. Not a defect. +needless_pass_by_value = { level = "allow", priority = 1 } +module_name_repetitions = { level = "allow", priority = 1 } +must_use_candidate = { level = "allow", priority = 1 } +missing_errors_doc = { level = "allow", priority = 1 } +missing_panics_doc = { level = "allow", priority = 1 } +cast_possible_truncation = { level = "allow", priority = 1 } +cast_possible_wrap = { level = "allow", priority = 1 } +cast_sign_loss = { level = "allow", priority = 1 } +cast_precision_loss = { level = "allow", priority = 1 } diff --git a/src/inspect.rs b/src/inspect.rs index 91809ef..0b94452 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -266,7 +266,7 @@ mod tests { // formula at 0, bidi at 6 ("=hello" is 6 bytes) let offsets: Vec = f.violations.iter().map(|v| v.byte_offset).collect(); let mut sorted = offsets.clone(); - sorted.sort(); + sorted.sort_unstable(); assert_eq!(offsets, sorted); } diff --git a/src/lib.rs b/src/lib.rs index beb7ee7..a530b9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/src/text.rs b/src/text.rs index a768664..1661b6c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -105,6 +105,14 @@ pub fn csv_field(input: I) -> Guarded { Guarded { value, lossy } } +/// Append the JSON `\uXXXX` escape for `c`. Writing to a `String` is infallible; +/// the `Result` exists only to satisfy the `fmt::Write` signature. +#[cfg(feature = "alloc")] +fn write_unicode_escape(out: &mut String, c: char) { + use core::fmt::Write as _; + let _ = write!(out, "\\u{:04x}", c as u32); +} + #[cfg(feature = "alloc")] pub fn jsonl_safe(input: I) -> Guarded { let (text, lossy) = input.as_utf8_lossy(); @@ -119,15 +127,13 @@ pub fn jsonl_safe(input: I) -> Guarded { '\n' => out.push_str("\\n"), '\x0C' => out.push_str("\\f"), '\r' => out.push_str("\\r"), - '\u{0000}'..='\u{0007}' | '\u{000B}' | '\u{000E}'..='\u{001F}' => { - out.push_str(&alloc::format!("\\u{:04x}", c as u32)); - } - '\u{007F}'..='\u{009F}' => { - out.push_str(&alloc::format!("\\u{:04x}", c as u32)); - } - c if is_bidi(c) => { - out.push_str(&alloc::format!("\\u{:04x}", c as u32)); - } + // C0 controls, DEL/C1 controls, and bidi overrides all take the same + // \uXXXX form; one arm keeps the escape set in a single place. + '\u{0000}'..='\u{0007}' + | '\u{000B}' + | '\u{000E}'..='\u{001F}' + | '\u{007F}'..='\u{009F}' => write_unicode_escape(&mut out, c), + c if is_bidi(c) => write_unicode_escape(&mut out, c), c => out.push(c), } } @@ -463,7 +469,7 @@ mod tests { #[test] fn jsonl_safe_escapes_backslash() { - let g = jsonl_safe(r#"C:\Users\foo"#); + let g = jsonl_safe(r"C:\Users\foo"); assert_eq!(g.to_string(), r#""C:\\Users\\foo""#); } diff --git a/src/types.rs b/src/types.rs index 37ba593..8dd9c83 100644 --- a/src/types.rs +++ b/src/types.rs @@ -83,10 +83,10 @@ impl Findings { pub fn is_csv_safe(&self) -> bool { !self.violations.iter().any(|v| match &v.kind { - ViolationKind::FormulaInjection => true, - ViolationKind::BidiOverride => true, - ViolationKind::InvalidUtf8 => true, - ViolationKind::ControlChar => !matches!(v.char, Some('\n') | Some('\r')), + ViolationKind::FormulaInjection + | ViolationKind::BidiOverride + | ViolationKind::InvalidUtf8 => true, + ViolationKind::ControlChar => !matches!(v.char, Some('\n' | '\r')), }) } diff --git a/tests/real_world_samples.rs b/tests/real_world_samples.rs index d67424b..e6dd74c 100644 --- a/tests/real_world_samples.rs +++ b/tests/real_world_samples.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used, clippy::expect_used)] + // Integration tests against real-world attack samples and authoritative test data. // Sources: // Unicode Consortium BidiCharacterTest.txt / BidiTest.txt (UCD 17.0.0)