persisted_value.rs (2765B)
1 //! Bounded projections for untrusted SQLite TEXT and BLOB values. 2 3 use sqlx::{Row, sqlite::SqliteRow}; 4 5 pub(crate) const MAX_IDENTIFIER_UTF8_BYTES: usize = 128; 6 pub(crate) const MAX_INTEGRITY_RESULT_BYTES: usize = 64; 7 pub(crate) const INTEGRITY_CHECK_SQL: &str = "PRAGMA integrity_check(1)"; 8 9 pub(crate) fn bounded_bytes<'row>( 10 row: &'row SqliteRow, 11 type_ok_column: &str, 12 length_column: &str, 13 prefix_column: &str, 14 minimum: usize, 15 maximum: usize, 16 ) -> Option<&'row [u8]> { 17 let type_ok = row.try_get::<i64, _>(type_ok_column).ok()? == 1; 18 let length = row.try_get::<Option<i64>, _>(length_column).ok()??; 19 let length = usize::try_from(length).ok()?; 20 let prefix = row.try_get::<Option<&'row [u8]>, _>(prefix_column).ok()??; 21 bounded_projection(type_ok, length, prefix, minimum, maximum) 22 } 23 24 pub(crate) fn bounded_utf8<'row>( 25 row: &'row SqliteRow, 26 type_ok_column: &str, 27 length_column: &str, 28 prefix_column: &str, 29 minimum: usize, 30 maximum: usize, 31 ) -> Option<&'row str> { 32 core::str::from_utf8(bounded_bytes( 33 row, 34 type_ok_column, 35 length_column, 36 prefix_column, 37 minimum, 38 maximum, 39 )?) 40 .ok() 41 } 42 43 pub(crate) fn bounded_integrity_bytes(row: &SqliteRow) -> Option<&[u8]> { 44 let value = row.try_get::<&[u8], _>(0).ok()?; 45 crate::all_constraints([!value.is_empty(), value.len() <= MAX_INTEGRITY_RESULT_BYTES]) 46 .then_some(value) 47 } 48 49 pub(crate) fn integrity_result_failed(row: &SqliteRow) -> Option<bool> { 50 let value = row.try_get::<&[u8], _>(0).ok()?; 51 (!value.is_empty()).then_some(value != b"ok") 52 } 53 54 fn bounded_projection( 55 type_ok: bool, 56 length: usize, 57 prefix: &[u8], 58 minimum: usize, 59 maximum: usize, 60 ) -> Option<&[u8]> { 61 crate::all_constraints([ 62 type_ok, 63 minimum <= maximum, 64 length >= minimum, 65 length <= maximum, 66 prefix.len() == length, 67 ]) 68 .then_some(prefix) 69 } 70 71 #[cfg(test)] 72 mod tests { 73 use super::*; 74 75 #[test] 76 fn bounded_projection_rejects_every_independent_drift() { 77 assert_eq!( 78 bounded_projection(true, 2, b"ok", 1, 64), 79 Some(b"ok".as_slice()) 80 ); 81 assert!(bounded_projection(false, 2, b"ok", 1, 64).is_none()); 82 assert!(bounded_projection(true, 2, b"ok", 65, 64).is_none()); 83 assert!(bounded_projection(true, 0, b"", 1, 64).is_none()); 84 assert!(bounded_projection(true, 65, &[b'x'; 65], 1, 64).is_none()); 85 assert!(bounded_projection(true, 2, b"x", 1, 64).is_none()); 86 } 87 88 #[test] 89 fn integrity_query_and_borrowed_byte_cap_are_exact() { 90 assert_eq!(INTEGRITY_CHECK_SQL, "PRAGMA integrity_check(1)"); 91 assert_eq!(MAX_INTEGRITY_RESULT_BYTES, 64); 92 } 93 }