| 1 | #![no_main] |
| 2 | //! Replaces a native text with arbitrary text: the edit either refuses it or appends a |
| 3 | //! revision storing it, leaving the old header's view of the file intact. |
| 4 | use libfuzzer_sys::fuzz_target; |
| 5 | use onestore::{ |
| 6 | ExGuid, RevisionIndex, Store, |
| 7 | document::{Document, Kind}, |
| 8 | op::{Op, PageOp}, |
| 9 | }; |
| 10 | use std::sync::LazyLock; |
| 11 | |
| 12 | #[path = "../../crates/onestore/tests/support/ops.rs"] |
| 13 | mod ops; |
| 14 | |
| 15 | const SOURCE: &[u8] = |
| 16 | include_bytes!("../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one"); |
| 17 | |
| 18 | fn text(bytes: &[u8], target: Option<(ExGuid, ExGuid)>) -> Vec<(ExGuid, ExGuid, String)> { |
| 19 | let store = Store::parse(bytes).unwrap(); |
| 20 | assert!(store.checksum_mismatches.is_empty()); |
| 21 | let index = RevisionIndex::parse(&store).unwrap(); |
| 22 | index.validate_current().unwrap(); |
| 23 | let document = Document::parse(&index).unwrap(); |
| 24 | let mut found = Vec::new(); |
| 25 | for (sid, space) in &document.spaces { |
| 26 | let revision = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 27 | for (oid, node) in &revision.nodes { |
| 28 | if let Kind::RichText { text, .. } = &node.kind |
| 29 | && target.is_none_or(|target| target == (*sid, *oid)) |
| 30 | { |
| 31 | found.push((*sid, *oid, text.clone())); |
| 32 | } |
| 33 | } |
| 34 | } |
| 35 | found |
| 36 | } |
| 37 | |
| 38 | static TARGET: LazyLock<(ExGuid, ExGuid)> = LazyLock::new(|| { |
| 39 | text(SOURCE, None) |
| 40 | .into_iter() |
| 41 | .find_map(|(sid, oid, text)| (text == "Fictitious plain text.").then_some((sid, oid))) |
| 42 | .expect("Missing native seed text") |
| 43 | }); |
| 44 | |
| 45 | fuzz_target!(|value: &[u8]| { |
| 46 | let (sid, oid) = *TARGET; |
| 47 | let value = String::from_utf8_lossy(value); |
| 48 | let op = PageOp::Text { |
| 49 | text: oid, |
| 50 | range: 0.."Fictitious plain text.".len() as u32, |
| 51 | with: value.to_string(), |
| 52 | }; |
| 53 | let edited = ops::apply(SOURCE, "Fuzz", vec![Op::Page { space: sid, op }]); |
| 54 | if value.contains(['\0', '\n', '\u{fffc}']) { |
| 55 | assert!(edited.is_err()); |
| 56 | return; |
| 57 | } |
| 58 | let bytes = edited.unwrap().image; |
| 59 | let mut before_commit = bytes.clone(); |
| 60 | before_commit[..1024].copy_from_slice(&SOURCE[..1024]); |
| 61 | assert_eq!( |
| 62 | text(&before_commit, Some((sid, oid)))[0].2, |
| 63 | "Fictitious plain text." |
| 64 | ); |
| 65 | assert_eq!(text(&bytes, Some((sid, oid)))[0].2, value); |
| 66 | }); |