1use std::fmt::Debug;
5use std::path::PathBuf;
6use std::str::FromStr;
7use std::{fs, thread};
8
9use log::warn;
10use rusqlite::{Connection, OptionalExtension, Transaction};
11use servo_base::generic_channel::{self, GenericReceiver, GenericSender};
12use servo_base::id::{BrowsingContextId, WebViewId};
13use servo_url::ImmutableOrigin;
14use storage_traits::client_storage::{
15 ClientStorageErrorr, ClientStorageThreadHandle, ClientStorageThreadMessage, Mode,
16 StorageIdentifier, StorageProxyMap, StorageType,
17};
18use uuid::Uuid;
19
20const STORAGE_SHELF_QUOTA_BYTES: u64 = 10 * 1024 * 1024 * 1024;
25
26trait RegistryEngine {
27 type Error: Debug;
28 fn create_database(
29 &mut self,
30 bottle_id: i64,
31 name: String,
32 ) -> Result<(PathBuf, bool), ClientStorageErrorr<Self::Error>>;
33 fn delete_database(
34 &mut self,
35 bottle_id: i64,
36 name: String,
37 ) -> Result<(), ClientStorageErrorr<Self::Error>>;
38 fn obtain_a_storage_bottle_map(
39 &mut self,
40 storage_type: StorageType,
41 webview: Option<WebViewId>,
42 storage_identifier: StorageIdentifier,
43 origin: ImmutableOrigin,
44 sender: &GenericSender<ClientStorageThreadMessage>,
45 ) -> Result<StorageProxyMap, ClientStorageErrorr<Self::Error>>;
46 fn persisted(&mut self, origin: ImmutableOrigin) -> Result<bool, String>;
47 fn persist(
48 &mut self,
49 origin: ImmutableOrigin,
50 permission_granted: bool,
51 ) -> Result<bool, String>;
52 fn estimate(&mut self, origin: ImmutableOrigin) -> Result<(u64, u64), String>;
53}
54
55struct SqliteEngine {
56 connection: Connection,
57 base_dir: PathBuf,
58}
59
60impl SqliteEngine {
61 fn new(base_dir: PathBuf) -> rusqlite::Result<Self> {
62 let db_path = base_dir.join("reg.sqlite");
63 let connection = Connection::open(db_path)?;
64 Self::init(&connection)?;
65 Ok(SqliteEngine {
66 connection,
67 base_dir,
68 })
69 }
70
71 fn memory() -> rusqlite::Result<Self> {
72 let connection = Connection::open_in_memory()?;
73 Self::init(&connection)?;
74 Ok(SqliteEngine {
75 connection,
76 base_dir: PathBuf::new(),
77 })
78 }
79
80 fn init(connection: &Connection) -> rusqlite::Result<()> {
81 connection.execute(r#"PRAGMA foreign_keys = ON;"#, [])?;
82 connection.execute(
83 r#"CREATE TABLE IF NOT EXISTS sheds (
84 id INTEGER PRIMARY KEY,
85 storage_type TEXT NOT NULL,
86 browsing_context TEXT
87 );"#,
88 [],
89 )?;
90
91 connection.execute(
93 r#"CREATE UNIQUE INDEX IF NOT EXISTS idx_sheds_local
94 ON sheds(storage_type) WHERE browsing_context IS NULL;"#,
95 [],
96 )?;
97 connection.execute(
98 r#"CREATE UNIQUE INDEX IF NOT EXISTS idx_sheds_session
99 ON sheds(browsing_context) WHERE browsing_context IS NOT NULL;"#,
100 [],
101 )?;
102
103 connection.execute(
104 r#"CREATE TABLE IF NOT EXISTS shelves (
105 id INTEGER PRIMARY KEY,
106 shed_id INTEGER NOT NULL,
107 origin TEXT NOT NULL,
108 UNIQUE (shed_id, origin),
109 FOREIGN KEY (shed_id) REFERENCES sheds(id) ON DELETE CASCADE
110 );"#,
111 [],
112 )?;
113
114 connection.execute(
116 r#"CREATE TABLE IF NOT EXISTS buckets (
117 id INTEGER PRIMARY KEY,
118 shelf_id INTEGER NOT NULL UNIQUE,
119 persisted BOOLEAN DEFAULT 0,
120 name TEXT,
121 mode TEXT,
122 expires DATETIME,
123 FOREIGN KEY (shelf_id) REFERENCES shelves(id) ON DELETE CASCADE
124 );"#,
125 [],
126 )?;
127
128 connection.execute(
130 r#"CREATE TABLE IF NOT EXISTS bottles (
131 id INTEGER PRIMARY KEY,
132 bucket_id INTEGER NOT NULL,
133 identifier TEXT NOT NULL, -- "idb", "ls", "opfs", "cache"
134 UNIQUE (bucket_id, identifier),
135 FOREIGN KEY (bucket_id) REFERENCES buckets(id) ON DELETE CASCADE
136 );"#,
137 [],
138 )?;
139
140 connection.execute(
141 r#"CREATE TABLE IF NOT EXISTS databases (
142 id INTEGER PRIMARY KEY,
143 bottle_id INTEGER NOT NULL,
144 name TEXT NOT NULL,
145 UNIQUE (bottle_id, name),
146 FOREIGN KEY (bottle_id) REFERENCES bottles(id) ON DELETE CASCADE
147 );
148 "#,
149 [],
150 )?;
151
152 connection.execute(
153 r#"CREATE TABLE IF NOT EXISTS directories (
154 id INTEGER PRIMARY KEY,
155 database_id INTEGER NOT NULL UNIQUE,
156 path TEXT NOT NULL,
157 FOREIGN KEY (database_id) REFERENCES databases(id) ON DELETE CASCADE
158 );"#,
159 [],
160 )?;
161
162 connection.execute_batch(
163 r#"
164 CREATE UNIQUE INDEX IF NOT EXISTS sheds_local_identity_idx
165 ON sheds(storage_type)
166 WHERE storage_type = 'local' AND browsing_context IS NULL;
167
168 CREATE UNIQUE INDEX IF NOT EXISTS sheds_session_identity_idx
169 ON sheds(storage_type, browsing_context)
170 WHERE storage_type = 'session' AND browsing_context IS NOT NULL;
171
172 CREATE UNIQUE INDEX IF NOT EXISTS shelves_origin_shed_identity_idx
173 ON shelves(origin, shed_id);
174 "#,
175 )?;
176 Ok(())
178 }
179}
180
181fn ensure_storage_shed(
182 storage_type: &StorageType,
183 browsing_context: Option<String>,
184 tx: &Transaction,
185) -> rusqlite::Result<i64> {
186 match browsing_context {
187 Some(browsing_context) => {
188 tx.execute(
189 "INSERT INTO sheds (storage_type, browsing_context) VALUES (?1, ?2) ON CONFLICT DO NOTHING;",
190 (storage_type.as_str(), browsing_context.as_str()),
191 )?;
192
193 tx.query_row(
194 "SELECT id FROM sheds WHERE storage_type = ?1 AND browsing_context = ?2;",
195 (storage_type.as_str(), browsing_context.as_str()),
196 |row| row.get(0),
197 )
198 },
199 None => {
200 tx.execute(
201 "INSERT INTO sheds (storage_type, browsing_context) VALUES (?1, NULL) ON CONFLICT DO NOTHING;",
202 [storage_type.as_str()],
203 )?;
204
205 tx.query_row(
206 "SELECT id FROM sheds WHERE storage_type = ?1 AND browsing_context IS NULL;",
207 [storage_type.as_str()],
208 |row| row.get(0),
209 )
210 },
211 }
212}
213
214fn create_a_storage_bucket(
216 shelf_id: i64,
217 storage_type: StorageType,
218 tx: &Transaction,
219) -> rusqlite::Result<i64> {
220 let bucket_id: i64 = if let StorageType::Local = storage_type {
223 tx.query_row(
224 "INSERT INTO buckets (mode, shelf_id) VALUES (?1, ?2)
225 ON CONFLICT(shelf_id) DO UPDATE SET shelf_id = excluded.shelf_id
226 RETURNING id;",
227 [Mode::default().as_str(), &shelf_id.to_string()],
228 |row| row.get(0),
229 )?
230 } else {
231 tx.query_row(
235 "INSERT INTO buckets (shelf_id) VALUES (?1)
236 ON CONFLICT(shelf_id) DO UPDATE SET shelf_id = excluded.shelf_id
237 RETURNING id;",
238 [&shelf_id.to_string()],
239 |row| row.get(0),
240 )?
241 };
242
243 let registered_endpoints = match storage_type {
249 StorageType::Local => vec![
250 StorageIdentifier::Caches,
251 StorageIdentifier::IndexedDB,
252 StorageIdentifier::LocalStorage,
253 StorageIdentifier::ServiceWorkerRegistrations,
254 ],
255 StorageType::Session => vec![StorageIdentifier::SessionStorage],
256 };
257
258 for identifier in registered_endpoints {
259 tx.execute(
260 "INSERT INTO bottles (bucket_id, identifier) VALUES (?1, ?2)
261 ON CONFLICT(bucket_id, identifier) DO NOTHING;",
262 (bucket_id, identifier.as_str()),
263 )?;
264 }
265
266 Ok(bucket_id)
268}
269
270fn create_a_storage_shelf(
272 shed: i64,
273 origin: &ImmutableOrigin,
274 storage_type: StorageType,
275 tx: &Transaction,
276) -> rusqlite::Result<StorageShelf> {
277 let shelf_id: i64 = tx.query_row(
281 "INSERT INTO shelves (shed_id, origin) VALUES (?1, ?2)
282 ON CONFLICT(shed_id, origin) DO UPDATE SET origin = excluded.origin
283 RETURNING id;",
284 [&shed.to_string(), &origin.ascii_serialization()],
285 |row| row.get(0),
286 )?;
287
288 Ok(StorageShelf {
290 default_bucket_id: create_a_storage_bucket(shelf_id, storage_type, tx)?,
291 })
292}
293
294fn obtain_a_storage_shelf(
296 shed: i64,
297 origin: &ImmutableOrigin,
298 storage_type: StorageType,
299 tx: &Transaction,
300) -> rusqlite::Result<StorageShelf> {
301 create_a_storage_shelf(shed, origin, storage_type, tx)
302}
303
304struct StorageShelf {
309 default_bucket_id: i64,
310}
311
312fn obtain_a_local_storage_shelf(
318 origin: &ImmutableOrigin,
319 tx: &Transaction,
320) -> Result<StorageShelf, String> {
321 if !origin.is_tuple() {
322 return Err("Storage is unavailable for opaque origins".to_owned());
323 }
324
325 let shed =
326 ensure_storage_shed(&StorageType::Local, None, tx).map_err(|error| error.to_string())?;
327 obtain_a_storage_shelf(shed, origin, StorageType::Local, tx).map_err(|error| error.to_string())
328}
329
330fn bucket_mode(bucket_id: i64, tx: &Transaction) -> rusqlite::Result<Mode> {
335 let mode: String = tx.query_row(
336 "SELECT mode FROM buckets WHERE id = ?1;",
337 [bucket_id],
338 |row| row.get(0),
339 )?;
340 Ok(Mode::from_str(&mode).unwrap_or_default())
341}
342
343fn set_bucket_mode(bucket_id: i64, mode: Mode, tx: &Transaction) -> rusqlite::Result<()> {
347 tx.execute(
348 "UPDATE buckets SET mode = ?1, persisted = ?2 WHERE id = ?3;",
349 (mode.as_str(), matches!(mode, Mode::Persistent), bucket_id),
350 )?;
351 Ok(())
352}
353
354fn storage_usage_for_bucket(bucket_id: i64, tx: &Transaction) -> Result<u64, String> {
362 let mut stmt = tx
363 .prepare(
364 "SELECT directories.path
365 FROM directories
366 JOIN databases ON directories.database_id = databases.id
367 JOIN bottles ON databases.bottle_id = bottles.id
368 WHERE bottles.bucket_id = ?1;",
369 )
370 .map_err(|error| error.to_string())?;
371
372 let rows = stmt
373 .query_map([bucket_id], |row| row.get::<_, String>(0))
374 .map_err(|error| error.to_string())?;
375
376 let mut usage = 0_u64;
377 for path in rows {
378 usage += directory_size(&PathBuf::from(path.map_err(|error| error.to_string())?))?;
379 }
380 Ok(usage)
381}
382
383fn storage_quota_for_bucket(_bucket_id: i64, _tx: &Transaction) -> Result<u64, String> {
395 Ok(STORAGE_SHELF_QUOTA_BYTES)
396}
397
398fn directory_size(path: &PathBuf) -> Result<u64, String> {
403 let metadata = fs::metadata(path).map_err(|error| error.to_string())?;
404 if metadata.is_file() {
405 return Ok(metadata.len());
406 }
407
408 if !metadata.is_dir() {
409 return Ok(0);
410 }
411
412 let mut size = 0_u64;
413 for entry in fs::read_dir(path).map_err(|error| error.to_string())? {
414 let entry = entry.map_err(|error| error.to_string())?;
415 size += directory_size(&entry.path())?;
416 }
417 Ok(size)
418}
419
420impl RegistryEngine for SqliteEngine {
421 type Error = rusqlite::Error;
422
423 fn create_database(
425 &mut self,
426 bottle_id: i64,
427 name: String,
428 ) -> Result<(PathBuf, bool), ClientStorageErrorr<Self::Error>> {
429 let tx = self.connection.transaction()?;
430
431 let database_id: i64 = tx
433 .query_row(
434 "INSERT INTO databases (bottle_id, name) VALUES (?1, ?2)
435 ON CONFLICT(bottle_id, name) DO UPDATE SET name = excluded.name
436 RETURNING id;",
437 (bottle_id, name),
438 |row| row.get(0),
439 )
440 .map_err(ClientStorageErrorr::Internal)?;
441
442 let existing_path: Option<String> = tx
443 .query_row(
444 "SELECT path FROM directories WHERE database_id = ?1;",
445 [database_id],
446 |row| row.get(0),
447 )
448 .optional()
449 .map_err(ClientStorageErrorr::Internal)?;
450
451 if let Some(p) = existing_path {
452 return Ok((PathBuf::from(p), false));
454 }
455
456 let dir = Uuid::new_v4().to_string();
457 let cluster = dir.chars().last().unwrap();
458 let path = self
459 .base_dir
460 .join("bottles")
461 .join(cluster.to_string())
462 .join(dir);
463
464 let path_str = path.to_str().ok_or_else(|| {
465 ClientStorageErrorr::Internal(rusqlite::Error::InvalidParameterName(String::from(
466 "path",
467 )))
468 })?;
469
470 tx.execute(
471 "INSERT INTO directories (database_id, path) VALUES (?1, ?2);",
472 (database_id, path_str),
473 )
474 .map_err(ClientStorageErrorr::Internal)?;
475
476 tx.commit().map_err(ClientStorageErrorr::Internal)?;
477
478 std::fs::create_dir_all(&path).map_err(|_| ClientStorageErrorr::DirectoryCreationFailed)?;
479
480 Ok((path, true))
481 }
482
483 fn delete_database(
485 &mut self,
486 bottle_id: i64,
487 name: String,
488 ) -> Result<(), ClientStorageErrorr<Self::Error>> {
489 let tx = self.connection.transaction()?;
490
491 let database_id: i64 = tx.query_row(
492 "SELECT id FROM databases WHERE bottle_id = ?1 AND name = ?2;",
493 (bottle_id, name.clone()),
494 |row| row.get(0),
495 )?;
496
497 let path: String = tx.query_row(
498 "SELECT path FROM directories WHERE database_id = ?1;",
499 [database_id],
500 |row| row.get(0),
501 )?;
502
503 tx.execute(
504 "DELETE FROM databases WHERE bottle_id = ?1 AND name = ?2;",
505 (bottle_id, name),
506 )?;
507
508 if tx.changes() == 0 {
509 return Err(ClientStorageErrorr::DatabaseDoesNotExist);
510 }
511 tx.commit()?;
514
515 std::fs::remove_dir_all(&path).map_err(|_| ClientStorageErrorr::DirectoryDeletionFailed)?;
519
520 Ok(())
521 }
522
523 fn obtain_a_storage_bottle_map(
525 &mut self,
526 storage_type: StorageType,
527 webview: Option<WebViewId>,
528 storage_identifier: StorageIdentifier,
529 origin: ImmutableOrigin,
530 sender: &GenericSender<ClientStorageThreadMessage>,
531 ) -> Result<StorageProxyMap, ClientStorageErrorr<Self::Error>> {
532 let tx = self.connection.transaction()?;
533
534 let shed_id: i64 = match storage_type {
536 StorageType::Local => {
537 ensure_storage_shed(&storage_type, None, &tx)?
539 },
540 StorageType::Session => {
541 let Some(webview) = webview else {
544 debug_assert!(false, "Session storage is only available on Window.");
545 return Err(ClientStorageErrorr::SessionStorageRequiresWindow);
546 };
547
548 ensure_storage_shed(
552 &storage_type,
553 Some(Into::<BrowsingContextId>::into(webview).to_string()),
554 &tx,
555 )?
556 },
557 };
558
559 let shelf = obtain_a_storage_shelf(shed_id, &origin, storage_type, &tx)?;
563
564 let bucket_id = shelf.default_bucket_id;
566
567 let bottle_id: i64 = tx.query_row(
568 "SELECT id FROM bottles WHERE bucket_id = ?1 AND identifier = ?2;",
569 (bucket_id, storage_identifier.as_str()),
570 |row| row.get(0),
571 )?;
572
573 tx.commit()?;
574
575 Ok(StorageProxyMap {
581 bottle_id,
582 handle: ClientStorageThreadHandle::new(sender.clone()),
583 })
584 }
585
586 fn persisted(&mut self, origin: ImmutableOrigin) -> Result<bool, String> {
587 let tx = self
588 .connection
589 .transaction()
590 .map_err(|error| error.to_string())?;
591
592 let shelf = obtain_a_local_storage_shelf(&origin, &tx)?;
596
597 let persisted = bucket_mode(shelf.default_bucket_id, &tx)
601 .is_ok_and(|mode| mode == Mode::Persistent) &&
602 tx.commit().is_ok();
603
604 Ok(persisted)
605 }
606
607 fn persist(
608 &mut self,
609 origin: ImmutableOrigin,
610 permission_granted: bool,
611 ) -> Result<bool, String> {
612 let tx = self
613 .connection
614 .transaction()
615 .map_err(|error| error.to_string())?;
616
617 let shelf = obtain_a_local_storage_shelf(&origin, &tx)?;
621
622 let bucket_id = shelf.default_bucket_id;
624
625 let mut persisted = bucket_mode(bucket_id, &tx).is_ok_and(|mode| mode == Mode::Persistent);
628
629 if !persisted && permission_granted {
633 persisted = set_bucket_mode(bucket_id, Mode::Persistent, &tx).is_ok();
634 }
635
636 if tx.commit().is_err() {
637 persisted = false;
638 }
639
640 Ok(persisted)
641 }
642
643 fn estimate(&mut self, origin: ImmutableOrigin) -> Result<(u64, u64), String> {
644 let tx = self
645 .connection
646 .transaction()
647 .map_err(|error| error.to_string())?;
648
649 let shelf = obtain_a_local_storage_shelf(&origin, &tx)?;
653
654 let usage = storage_usage_for_bucket(shelf.default_bucket_id, &tx)?;
656 let quota = storage_quota_for_bucket(shelf.default_bucket_id, &tx)?;
658
659 tx.commit().map_err(|error| error.to_string())?;
660
661 Ok((usage, quota))
662 }
663}
664
665pub trait ClientStorageThreadFactory {
666 fn new(config_dir: Option<PathBuf>, temporary_storage: bool) -> Self;
667}
668
669impl ClientStorageThreadFactory for ClientStorageThreadHandle {
670 fn new(config_dir: Option<PathBuf>, temporary_storage: bool) -> ClientStorageThreadHandle {
671 let (generic_sender, generic_receiver) = generic_channel::channel().unwrap();
672 let mut temp_dir: Option<tempfile::TempDir> = None;
673 let base_dir = config_dir
674 .unwrap_or_else(|| {
675 let tmp_dir = tempfile::tempdir().unwrap();
676 let path = tmp_dir.path().to_path_buf();
677 temp_dir = Some(tmp_dir);
678 path
679 })
680 .join("clientstorage");
681 let storage_dir = if temporary_storage {
682 let unique_id = uuid::Uuid::new_v4().to_string();
683 base_dir.join("temporary").join(unique_id)
684 } else {
685 base_dir.join("default_v1")
686 };
687 std::fs::create_dir_all(&storage_dir)
688 .expect("Failed to create ClientStorage storage directory");
689 let sender_clone = generic_sender.clone();
690 thread::Builder::new()
691 .name("ClientStorageThread".to_owned())
692 .spawn(move || {
693 let _ = temp_dir;
695 let engine = SqliteEngine::new(storage_dir).unwrap_or_else(|error| {
696 warn!("Failed to initialize ClientStorage engine into storage dir: {error:?}");
697 SqliteEngine::memory().unwrap()
698 });
699 ClientStorageThread::new(sender_clone, generic_receiver, engine).start();
700 })
701 .expect("Thread spawning failed");
702
703 ClientStorageThreadHandle::new(generic_sender)
704 }
705}
706
707struct ClientStorageThread<E: RegistryEngine> {
708 receiver: GenericReceiver<ClientStorageThreadMessage>,
709 sender: GenericSender<ClientStorageThreadMessage>,
710 engine: E,
711}
712
713impl<E> ClientStorageThread<E>
714where
715 E: RegistryEngine,
716{
717 pub fn new(
718 sender: GenericSender<ClientStorageThreadMessage>,
719 receiver: GenericReceiver<ClientStorageThreadMessage>,
720 engine: E,
721 ) -> ClientStorageThread<E> {
722 ClientStorageThread {
723 sender,
724 receiver,
725 engine,
726 }
727 }
728
729 pub fn start(&mut self) {
730 while let Ok(message) = self.receiver.recv() {
731 match message {
732 ClientStorageThreadMessage::ObtainBottleMap {
733 storage_type,
734 storage_identifier,
735 webview,
736 origin,
737 sender,
738 } => {
739 let result = self.engine.obtain_a_storage_bottle_map(
740 storage_type,
741 webview,
742 storage_identifier,
743 origin,
744 &self.sender,
745 );
746 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
747 },
748 ClientStorageThreadMessage::CreateDatabase {
749 bottle_id,
750 name,
751 sender,
752 } => {
753 let result = self.engine.create_database(bottle_id, name);
754 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
755 },
756 ClientStorageThreadMessage::DeleteDatabase {
757 bottle_id,
758 name,
759 sender,
760 } => {
761 let result = self.engine.delete_database(bottle_id, name);
762 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
763 },
764 ClientStorageThreadMessage::Persisted { origin, sender } => {
765 let _ = sender.send(self.engine.persisted(origin));
766 },
767 ClientStorageThreadMessage::Persist {
768 origin,
769 permission_granted,
770 sender,
771 } => {
772 let _ = sender.send(self.engine.persist(origin, permission_granted));
773 },
774 ClientStorageThreadMessage::Estimate { origin, sender } => {
775 let _ = sender.send(self.engine.estimate(origin));
776 },
777 ClientStorageThreadMessage::Exit(sender) => {
778 let _ = sender.send(());
779 break;
780 },
781 }
782 }
783 }
784}