autofill/db/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5pub mod addresses;
6pub mod credit_cards;
7pub mod models;
8pub mod passports;
9pub mod schema;
10pub mod store;
11
12use crate::error::*;
13
14use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
15use rusqlite::{Connection, OpenFlags};
16use sql_support::open_database;
17use std::sync::Arc;
18use std::{
19    ops::{Deref, DerefMut},
20    path::{Path, PathBuf},
21};
22use url::Url;
23
24pub struct AutofillDb {
25    pub writer: Connection,
26    interrupt_handle: Arc<SqlInterruptHandle>,
27}
28
29impl AutofillDb {
30    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
31        let db_path = normalize_path(db_path)?;
32        Self::new_named(db_path)
33    }
34
35    pub fn new_memory(db_path: &str) -> Result<Self> {
36        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
37        Self::new_named(name)
38    }
39
40    fn new_named(db_path: PathBuf) -> Result<Self> {
41        // We always create the read-write connection for an initial open so
42        // we can create the schema and/or do version upgrades.
43        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
44            | OpenFlags::SQLITE_OPEN_URI
45            | OpenFlags::SQLITE_OPEN_CREATE
46            | OpenFlags::SQLITE_OPEN_READ_WRITE;
47
48        let conn = open_database::open_database_with_flags(
49            db_path,
50            flags,
51            &schema::AutofillConnectionInitializer,
52        )?;
53
54        Ok(Self {
55            interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
56            writer: conn,
57        })
58    }
59
60    #[inline]
61    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
62        Ok(self.interrupt_handle.begin_interrupt_scope()?)
63    }
64}
65
66impl Deref for AutofillDb {
67    type Target = Connection;
68
69    fn deref(&self) -> &Self::Target {
70        &self.writer
71    }
72}
73
74impl DerefMut for AutofillDb {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        &mut self.writer
77    }
78}
79
80fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
81    p.as_ref()
82        .to_str()
83        .and_then(|s| Url::parse(s).ok())
84        .and_then(|u| {
85            if u.scheme() == "file" {
86                u.to_file_path().ok()
87            } else {
88                None
89            }
90        })
91        .unwrap_or_else(|| p.as_ref().to_owned())
92}
93
94fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
95    let path = unurl_path(p);
96    if let Ok(canonical) = path.canonicalize() {
97        return Ok(canonical);
98    }
99    // It probably doesn't exist yet. This is an error, although it seems to
100    // work on some systems.
101    //
102    // We resolve this by trying to canonicalize the parent directory, and
103    // appending the requested file name onto that. If we can't canonicalize
104    // the parent, we return an error.
105    //
106    // Also, we return errors if the path ends in "..", if there is no
107    // parent directory, etc.
108    let file_name = path
109        .file_name()
110        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
111
112    let parent = path
113        .parent()
114        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
115
116    let mut canonical = parent.canonicalize()?;
117    canonical.push(file_name);
118    Ok(canonical)
119}
120
121pub(crate) mod sql_fns {
122    use rusqlite::{functions::Context, Result};
123    use sync_guid::Guid as SyncGuid;
124    use types::Timestamp;
125
126    #[inline(never)]
127    #[allow(dead_code)]
128    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
129        Ok(SyncGuid::random())
130    }
131
132    #[inline(never)]
133    pub fn now(_ctx: &Context<'_>) -> Result<Timestamp> {
134        Ok(Timestamp::now())
135    }
136}
137
138// Helpers for tests
139#[cfg(test)]
140pub mod test {
141    use super::*;
142    use std::sync::atomic::{AtomicUsize, Ordering};
143
144    // A helper for our tests to get their own memory Api.
145    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
146
147    pub fn new_mem_db() -> AutofillDb {
148        error_support::init_for_tests();
149        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
150        AutofillDb::new_memory(&format!("test_autofill-api-{}", counter))
151            .expect("should get an API")
152    }
153}