1use std::{
7 ffi::CStr,
8 os::raw::{c_char, c_int, c_void},
9 sync::atomic::{AtomicU64, Ordering},
10};
11
12use anyhow::{Result, ensure};
13use libsqlite3_sys::{
14 SQLITE_ATTACH, SQLITE_DENY, SQLITE_LIMIT_LENGTH, SQLITE_OK, SQLITE_PRAGMA, sqlite3,
15 sqlite3_limit, sqlite3_progress_handler, sqlite3_set_authorizer,
16};
17
18const PROGRESS_INTERVAL: c_int = 1000;
20
21const DENIED_PRAGMA_WRITES: &[&str] = &[
23 "temp_store",
24 "temp_store_directory",
25 "data_store_directory",
26 "max_page_count",
27 "page_size",
28 "journal_mode",
29 "synchronous",
30 "locking_mode",
31 "cache_spill",
32 "auto_vacuum",
33 "journal_size_limit",
34];
35
36#[derive(Clone, Copy, Debug)]
37pub struct ConnectionGuard {
38 pub max_page_count: u32,
40 pub max_length_bytes: i32,
42 pub max_progress_callbacks: u64,
45 pub max_result_bytes: u64,
48}
49
50impl Default for ConnectionGuard {
51 fn default() -> Self {
52 Self {
53 max_page_count: 65_536,
54 max_length_bytes: 24 * 1024 * 1024,
55 max_progress_callbacks: 100_000,
56 max_result_bytes: 24 * 1024 * 1024,
57 }
58 }
59}
60
61pub(crate) struct InstalledGuard {
64 config: ConnectionGuard,
65 progress_callbacks: AtomicU64,
66}
67
68impl InstalledGuard {
69 pub(crate) fn new(config: ConnectionGuard) -> Box<Self> {
70 Box::new(Self {
71 config,
72 progress_callbacks: AtomicU64::new(0),
73 })
74 }
75
76 pub(crate) fn install(&self, db: *mut sqlite3) -> Result<()> {
79 crate::vfs::sqlite_exec(
80 db,
81 &format!("PRAGMA max_page_count = {};", self.config.max_page_count),
82 )
83 .map_err(anyhow::Error::msg)?;
84 unsafe {
85 sqlite3_limit(db, SQLITE_LIMIT_LENGTH, self.config.max_length_bytes);
86 sqlite3_progress_handler(
87 db,
88 PROGRESS_INTERVAL,
89 Some(progress),
90 self as *const Self as *mut c_void,
91 );
92 }
93 let rc = unsafe { sqlite3_set_authorizer(db, Some(authorize), std::ptr::null_mut()) };
94 ensure!(
95 rc == SQLITE_OK,
96 "failed to install the SQLite authorizer: {rc}"
97 );
98 Ok(())
99 }
100
101 pub(crate) fn max_result_bytes(&self) -> u64 {
103 self.config.max_result_bytes
104 }
105
106 pub(crate) fn reset(&self) {
108 self.progress_callbacks.store(0, Ordering::Relaxed);
109 }
110}
111
112extern "C" fn progress(context: *mut c_void) -> c_int {
113 let guard = unsafe { &*(context as *const InstalledGuard) };
114 let callbacks = guard.progress_callbacks.fetch_add(1, Ordering::Relaxed) + 1;
115 c_int::from(callbacks > guard.config.max_progress_callbacks)
116}
117
118extern "C" fn authorize(
119 _context: *mut c_void,
120 action: c_int,
121 first: *const c_char,
122 second: *const c_char,
123 _database: *const c_char,
124 _trigger: *const c_char,
125) -> c_int {
126 let text = |value: *const c_char| -> Option<String> {
127 if value.is_null() {
128 return None;
129 }
130 Some(
131 unsafe { CStr::from_ptr(value) }
132 .to_string_lossy()
133 .into_owned(),
134 )
135 };
136 match action {
137 SQLITE_ATTACH if text(first).is_some_and(|name| !name.is_empty()) => SQLITE_DENY,
139 SQLITE_PRAGMA if second.is_null() => SQLITE_OK,
140 SQLITE_PRAGMA => {
141 let name = text(first).unwrap_or_default().to_ascii_lowercase();
142 if DENIED_PRAGMA_WRITES.contains(&name.as_str()) {
143 SQLITE_DENY
144 } else {
145 SQLITE_OK
146 }
147 }
148 _ => SQLITE_OK,
149 }
150}